eco-helpers 2.5.4 → 2.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 444bec4cd18402a38eff718340932ca7d72c9959b865805964b0ec9787d94636
4
- data.tar.gz: 97291544f70b525781d81f168ffc86c1d960151e53535a540ac1b7824b9eaf28
3
+ metadata.gz: ed268fb73bb35106da813f51a7b9469d0ee0fe167cee520ac44f98fed22c622b
4
+ data.tar.gz: c6b6e0f72037f2a1dac9313293317a78a4d25c52568c7577f88efdafb37d7994
5
5
  SHA512:
6
- metadata.gz: 02360de987395d801aa1b1fb659dd4b6011c317a21058be33c3283b3a2856c69db519baf6eb85a022f7a2c7c4e826861b108c2cf09aefad3ca57b32d53b45c72
7
- data.tar.gz: 402c880d701446c072f6534d4df35b2a104704dc79480e37e5033101df63a8b10baa25257911af1cc84994efe05744b15c608798dbc1637ad268811ca2d7d926
6
+ metadata.gz: 21d9f1703574866e548daa22f1e196b237a8415fb750fe53bb6c4cd63e5fa90002a9e2d5f90e5f3aaca769f639e6d9c02f40b0e7a7a123a15965af41bff91828
7
+ data.tar.gz: a02e32a2945f0e4e7a5780e340e8ef400e434d00f2cf44d9969d8573bf84e94c5d35c4c0319e205e351837dc016eb9e389759db3d2291ceab493943af597170b
data/CHANGELOG.md CHANGED
@@ -1,12 +1,45 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
- ## [2.5.5] - 2023-07-xx
4
+ ## [2.5.7] - 2023-08-xx
5
5
 
6
6
  ### Added
7
7
  ### Changed
8
8
  ### Fixed
9
9
 
10
+ ## [2.5.6] - 2023-08-14
11
+
12
+ ### Changed
13
+ - `Eco::API::Session::Config::Tagtree#live_tree` remove memmoize tree: it now always trigger a query to the back-end
14
+ - Better messaging to know what's going on.
15
+
16
+ ### Fixed
17
+ - `Eco::API::Session#live_tree` was not forwarding target structure id `id`
18
+ - `Eco::Data::Locations::NodeLevel#override_lower_levels`
19
+ - By **default** should only override empty upper levels
20
+ - `Eco::Data::Locations::NodeLevel::Cleaner`
21
+ - `#tidy_nodes` when **gap** (merged parent) identified, `level` is still correct
22
+ - `Eco::Data::Locations::NodeLevel`
23
+ - `#nodes_from_csv` correctly identify parent with big gaps
24
+ - This fix ensures a correct normalization
25
+ - `Eco::API::UseCases::GraphQL::Samples::Location::Command::Result`
26
+ - **Batch remap tags** _built_ requires full path to keep from-filter specificity
27
+ - This change also wraps into a new class the remap tags table.
28
+
29
+ ## [2.5.5] - 2023-08-03
30
+
31
+ ### Added
32
+ - `Eco::API::Organization::TagTree` - **added/improved methods**
33
+ - `#as_json` new parameter `max_depth:` to be able to cut the tree
34
+ - `#active_tree` new method to exclude archived nodes.
35
+ - `#truncate` new method to obtain a tree cut to `max_depth:`
36
+ - `Eco::API::Session::Config::Tagtree`: **exposed** `include_archived:` (a.k.a. `inludeArchivedNodes`).
37
+ - This change states that `session.tagtree` does not retrieve archived nodes by default, while `session.live_tree` does retrieve archived nodes.
38
+ - This change required an update on the `ecoportal-api-graphql` **gem**
39
+
40
+ ### Fixed
41
+ - `Eco::API::Organization::TagTree#path` - dups the result (rather than exposing the internal path array)
42
+
10
43
  ## [2.5.4] - 2023-07-27
11
44
 
12
45
  ### Added
data/eco-helpers.gemspec CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
32
32
 
33
33
  spec.add_dependency 'ecoportal-api', '>= 0.9.4', '< 0.10'
34
34
  spec.add_dependency 'ecoportal-api-v2', '>= 1.1.3', '< 1.2'
35
- spec.add_dependency 'ecoportal-api-graphql', '>= 0.3.8', '< 0.4'
35
+ spec.add_dependency 'ecoportal-api-graphql', '>= 0.3.10', '< 0.4'
36
36
  spec.add_dependency 'aws-sdk-s3', '>= 1.83.0', '< 2'
37
37
  spec.add_dependency 'aws-sdk-ses', '>= 1.36.0', '< 2'
38
38
  spec.add_dependency 'dotenv', '>= 2.7.6', '< 3'
@@ -523,7 +523,6 @@ module Eco
523
523
  call_order = parsing? ? [ext, mad, int, fin] : [fin, int, mad, ext]
524
524
  call_order.each {|proc| proc.call}
525
525
  end
526
-
527
526
  end
528
527
  end
529
528
  end
@@ -76,9 +76,23 @@ module Eco
76
76
  @archived
77
77
  end
78
78
 
79
+ def active?
80
+ !archived?
81
+ end
82
+
79
83
  # @return [Eco::API::Organization::TagTree]
80
84
  def dup
81
- self.class.new(as_json)
85
+ self.class.new(as_json, name: name, id: id)
86
+ end
87
+
88
+ # @return [Eco::API::Organization::TagTree] with **non** `archived` nodes only
89
+ def active_tree
90
+ self.class.new(as_json(include_archived: false), name: name, id: id)
91
+ end
92
+
93
+ # @return [Eco::API::Organization::TagTree] with nodes up to `max_depth`
94
+ def truncate(max_depth: total_depth)
95
+ self.class.new(as_json(max_depth: max_depth), name: name, id: id)
82
96
  end
83
97
 
84
98
  # Iterate through all the nodes of this tree
@@ -161,11 +175,26 @@ module Eco
161
175
  # Returns a tree of Hashes form nested via `nodes` (or just a list of hash nodes)
162
176
  # @yield [node_json, node] block for custom output json model
163
177
  # @yiledreturn [Hash] the custom json model.
164
- # @include_children [Boolean] whether it should return a tree hash or just a list of hash nodes.
178
+ # @param include_children [Boolean] whether it should return a tree hash or just a list of hash nodes.
179
+ # @param include_archived [Boolean] whether it should include archived nodes.
180
+ # @param max_depth [Boolean] up to what level `depth` nodes should be included.
165
181
  # @return [Array[Hash]] where `Hash` is a `node` (i.e. `{"tag" => TAG, "nodes": Array[Hash]}`)
166
- def as_json(include_children: true, &block)
167
- return [] if top? && !include_children
168
- children_json = nodes.map {|nd| nd.as_json(include_children: true, &block)} if include_children
182
+ def as_json(include_children: true, include_archived: true, max_depth: total_depth, &block)
183
+ max_depth ||= total_depth
184
+ return nil if max_depth < depth
185
+ return [] if top? && !include_children
186
+ return nil if archived? && !include_archived
187
+
188
+ if include_children
189
+ child_nodes = nodes
190
+ child_nodes = child_nodes.select(&:active?) unless include_archived
191
+ kargs = {
192
+ include_children: include_children,
193
+ include_archived: include_archived,
194
+ max_depth: max_depth
195
+ }
196
+ children_json = child_nodes.map {|nd| nd.as_json(**kargs, &block)}.compact
197
+ end
169
198
 
170
199
  if top?
171
200
  children_json
@@ -287,8 +316,8 @@ module Eco
287
316
  # @param key [String] tag to find the path to.
288
317
  # @return [Array<String>]
289
318
  def path(key = nil)
290
- return @path if !key
291
- @hash_paths[key.upcase]
319
+ return @path.dup if !key
320
+ @hash_paths[key.upcase].dup
292
321
  end
293
322
 
294
323
  # Helper to assign tags to a person account.
@@ -3,18 +3,31 @@ module Eco
3
3
  class Session
4
4
  class Config
5
5
  class TagTree < BaseConfig
6
+ class MissingTagtree < StandardError
7
+ end
8
+
6
9
  attr_key :file
7
10
 
8
- def scope_tree(enviro: nil)
11
+ # @param include_archived [Boolean] whether or not it should include archived nodes.
12
+ # @return [Eco::API::Organization::TagTree]
13
+ def scope_tree(enviro: nil, include_archived: true, raise_on_missing: true)
9
14
  return @tagtree if instance_variable_defined?(:@tagtree) && @tagtree.enviro == enviro
15
+
10
16
  if tree_file = self.file
11
17
  if (tree = file_manager.load_json(tree_file)) && !tree.empty?
12
18
  @tagtree = Eco::API::Organization::TagTree.new(tree, enviro: enviro)
13
19
  end
14
20
  end
15
- @tagtree ||= live_tree(enviro: enviro).tap do |tree|
21
+
22
+ kargs = {
23
+ enviro: enviro,
24
+ includeArchivedNodes: include_archived
25
+ }
26
+
27
+ @tagtree ||= live_tree(**kargs).tap do |tree|
16
28
  unless tree && !tree.empty?
17
- raise "Could not find a locations structure."
29
+ msg = "Could not find a local or live locations structure."
30
+ raise MissingTagtree, msg
18
31
  end
19
32
  end
20
33
  end
@@ -22,14 +35,25 @@ module Eco
22
35
  # Among all the locations structures it selects the one with more location nodes
23
36
  # If `id` is provided, it only retrieves this locations structure.
24
37
  def live_tree(id: nil, enviro: nil, include_archived: false, **kargs, &block)
25
- return @live_tree if instance_variable_defined?(:@live_tree) && @live_tree.enviro == enviro
38
+ existing_cache = !!@live_tree
39
+ first_load = !existing_cache
40
+
41
+ target_change = existing_cache && id && @live_tree.id != id
42
+ enviro_change = existing_cache && enviro && @live_tree.enviro != enviro
43
+
44
+ switching_target = existing_cache && (target_change || enviro_change)
45
+ refresh_cache = existing_cache && !switching_target
46
+
47
+ kargs = {
48
+ enviro: enviro,
49
+ includeArchivedNodes: include_archived
50
+ }.merge(kargs)
51
+
26
52
  if id
27
- args = {id: id, enviro: enviro, include_archived: include_archived}.merge(kargs)
53
+ args = { id: id }.merge(kargs)
28
54
  @live_tree = live_tree_get(**args, &block)
29
55
  else
30
- # note that `include_archived` nodes is NOT the same as including archived structures
31
- # => In `live_tree` the paramter refers to nodes
32
- trees = live_trees(enviro: enviro, &block)
56
+ trees = live_trees(**kargs, &block)
33
57
  @live_tree = trees.reject do |tree|
34
58
  tree.empty?
35
59
  end.max do |a,b|
@@ -37,34 +61,50 @@ module Eco
37
61
  end
38
62
  end.tap do |tree|
39
63
  if tree
40
- msg = "Using LIVE LOCATIONS Structure: '#{tree.name}' (#{tree.count} nodes)"
41
- session_logger.info(msg)
64
+ msg = "LIVE LOCATIONS Structure (#{tree.id}): '#{tree.name}' (#{tree.count} nodes)"
65
+ if first_load
66
+ session_logger.info("Using #{msg}")
67
+ elsif switching_target
68
+ session_logger.info("Switched to #{msg}")
69
+ else # refresh_cache
70
+ session_logger.debug("Reloading #{msg}")
71
+ end
72
+ else
73
+ session_logger.info "Could not retrive live tree (#{id})"
42
74
  end
43
75
  end
44
76
  end
45
77
 
46
78
  # Gets a single locations structure
47
79
  # @note it does not memoize
80
+ # @param include_archived [Boolean] whether or not to include archived **nodes**
81
+ # @return [Eco::API::Organization::TagTree, NilClass]
48
82
  def live_tree_get(id: nil, enviro: nil, include_archived: false, **kargs, &block)
49
83
  return nil unless apis.active_api.version_available?(:graphql)
50
84
  return nil unless graphql = apis.api(version: :graphql)
51
- #kargs = { includeArchived: include_archived }.merge(kargs).slice(:includeArchived)
52
- # For now, this endpoint only accepts `id` as a parameter. It is pending to
53
- # expose further parameters via query
54
- return nil unless tree = graphql.currentOrganization.locationStructure(id: id, &block)
85
+
86
+ kargs = {
87
+ id: id,
88
+ includeArchivedNodes: include_archived
89
+ }.merge(kargs)
90
+
91
+ return nil unless tree = graphql.currentOrganization.locationStructure(**kargs, &block)
92
+
55
93
  args = { enviro: enviro, id: tree.id, name: tree.name }
56
94
  Eco::API::Organization::TagTree.new(tree.treeify, **args)
57
95
  end
58
96
 
59
97
  # Retrieves all the location structures of the organisation
98
+ # @param include_archived [Boolean] whether or not to include archived **nodes**
99
+ # @return [Array<Eco::API::Organization::TagTree>]
60
100
  def live_trees(enviro: nil, include_archived: false, **kargs, &block)
61
101
  [].tap do |eco_trees|
62
102
  next unless apis.active_api.version_available?(:graphql)
63
103
  next unless graphql = apis.api(version: :graphql)
104
+
64
105
  kargs = {
65
- includeArchived: include_archived,
66
- includeUnpublished: false
67
- }.merge(kargs).slice(:includeArchived, :includeUnpublished)
106
+ includeArchivedNodes: include_archived
107
+ }.merge(kargs)
68
108
 
69
109
  next unless trees = graphql.currentOrganization.locationStructures(**kargs, &block)
70
110
  trees.each do |tree|
@@ -235,20 +235,22 @@ module Eco
235
235
  end
236
236
 
237
237
  # It uses the `tagtree.json` file and in its absence, if `graphql` enabled, the largest `life_tagtree`
238
+ # @note it does NOT include archived nodes by default.
239
+ # - This is for legacy (most usecases don't)
240
+ # @param include_archived [Boolean] whether or not it should include archived nodes.
238
241
  # @return [Eco::API::Organization::TagTree]
239
- def tagtree(enviro: nil)
240
- @tagtree ||= tagtree_config.scope_tree(enviro: enviro)
242
+ def tagtree(enviro: nil, include_archived: false, raise_on_missing: true)
243
+ kargs = {
244
+ enviro: enviro, include_archived: include_archived, raise_on_missing: raise_on_missing
245
+ }
246
+ @tagtree ||= tagtree_config.scope_tree(**kargs)
241
247
  end
242
248
 
243
249
  # It obtains the first of the live tagtree in the org
244
250
  # @note it requires graphql connection configuration parameters
245
251
  # @return [Eco::API::Organization::TagTree]
246
252
  def live_tree(id: nil, enviro: nil, **kargs, &block)
247
- if id
248
- tagtree_config.live_tree_get(id: id, enviro: enviro, **kargs, &block)
249
- else
250
- tagtree_config.live_tree(enviro: enviro, **kargs, &block)
251
- end
253
+ tagtree_config.live_tree(id: id, enviro: enviro, **kargs, &block)
252
254
  end
253
255
 
254
256
  # @return [Eco::API::Organization::PolicyGroups]
@@ -46,7 +46,7 @@ module Eco
46
46
 
47
47
  # @see Eco::API::Session::Config#live_tree
48
48
  def live_tree(id: nil, include_archived: false, **kargs, &block)
49
- config.live_tree(id: nil, include_archived: include_archived, enviro: enviro, **kargs, &block)
49
+ config.live_tree(id: id, include_archived: include_archived, enviro: enviro, **kargs, &block)
50
50
  end
51
51
 
52
52
  # @see Eco::API::Session::Config#schemas
@@ -12,9 +12,9 @@ class Eco::API::UseCases::DefaultCases::ReinviteTransCase < Eco::API::Common::Lo
12
12
  invite = session.new_job("main", "invite", :update, usecase, :account)
13
13
  users.each do |person|
14
14
  if force_invite?
15
- person.account.send_invites = true
16
- else
17
15
  person.account.force_send_invites = true
16
+ else
17
+ person.account.send_invites = true
18
18
  end
19
19
  invite.add(person)
20
20
  end
@@ -68,7 +68,7 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
68
68
  msg << "nor options(:source, :structure_id). "
69
69
  msg << "Infering active locations structure."
70
70
  log(:warn) { msg }
71
- if self.current_tree = session.live_tree
71
+ if self.current_tree = session_live_tree
72
72
  @target_structure_id = current_tree.id
73
73
  end
74
74
  end
@@ -81,7 +81,13 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
81
81
  tree_init = current_tree
82
82
  target_id = target_structure_id
83
83
  return current_tree if current_tree != tree_init
84
- self.current_tree = session.live_tree(id: target_id, include_archived: true)
84
+ self.current_tree = session_live_tree(id: target_id)
85
+ end
86
+
87
+ # Unique access point to retrieve the live tree
88
+ # @note ensures archived nodes are retrieved.
89
+ def session_live_tree(id: nil)
90
+ session.live_tree(id: id, include_archived: true)
85
91
  end
86
92
  end
87
93
  end
@@ -0,0 +1,66 @@
1
+ module Eco::API::UseCases::GraphQL::Helpers::Location
2
+ class TagsRemap
3
+ class TagsMap
4
+ attr_reader :from, :to
5
+
6
+ def initialize(from, to)
7
+ @from = TagsSet.new(from)
8
+ @to = TagsSet.new(to)
9
+ end
10
+
11
+ def to_csv_row
12
+ [from.to_s, to.to_s]
13
+ end
14
+
15
+ # @note to create a stable sort we assume `self` is a sorted element
16
+ def <=>(other)
17
+ return -1 if maps? && !other.maps?
18
+ return 1 if !maps? && other.maps?
19
+ return -1 if rename? && other.move?
20
+ return 1 if move? && other.rename?
21
+ return -1 if rename? && other.rename?
22
+ # both are being moved (specific/long mappings first)
23
+ return 1 if from.subset_of?(other.from)
24
+ return -1 if from.superset_of?(other.from)
25
+ return -1 if (from & other.from).empty?
26
+ return -1 if from.length >= other.from.length
27
+ return 1 if from.length < other.from.length
28
+ -1
29
+ end
30
+
31
+ # @note to create a stable sort we assume `self` is a sorted element
32
+ def goes_before?(other)
33
+ (self <=> other) == -1
34
+ end
35
+
36
+ # @note to create a stable sort we assume `self` is a sorted element
37
+ def goes_after?(other)
38
+ (self <=> other) == 1
39
+ end
40
+
41
+ def both?(&block)
42
+ [from, to].all?(&block)
43
+ end
44
+
45
+ def any?(&block)
46
+ [from, to].any?(&block)
47
+ end
48
+
49
+ def maps?
50
+ return false if any?(&:empty?)
51
+ return false if from == to
52
+ true
53
+ end
54
+
55
+ def rename?
56
+ return false unless maps?
57
+ both? {|set| set.length == 1}
58
+ end
59
+
60
+ def move?
61
+ return false unless maps?
62
+ !rename?
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,95 @@
1
+ module Eco::API::UseCases::GraphQL::Helpers::Location
2
+ class TagsRemap
3
+ class TagsSet
4
+ class << self
5
+ def attr_compare(*attrs)
6
+ attrs.each do |attr|
7
+ meth = "#{attr}".to_sym
8
+ define_method meth do |value|
9
+ set.send(meth, to_set(value))
10
+ end
11
+ end
12
+ end
13
+ def attr_operate(*attrs)
14
+ attrs.each do |attr|
15
+ meth = "#{attr}".to_sym
16
+ define_method meth do |value|
17
+ self.class.new(set.send(meth, to_set(value)))
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ COMPARE_METHODS = %i[< <= == > >=].freeze
24
+ OPERATE_METHIDS = %i[& + - ^].freeze
25
+
26
+ attr_compare *COMPARE_METHODS
27
+ attr_operate *OPERATE_METHIDS
28
+
29
+ attr_reader :tags, :set
30
+
31
+ def initialize(tags)
32
+ @ini_tags = value_to_a(tags)
33
+ @set = to_set(tags)
34
+ end
35
+
36
+ def length
37
+ set.length
38
+ end
39
+
40
+ def tags
41
+ ini_tags.compact.map(&:upcase)
42
+ end
43
+
44
+ def to_a
45
+ tags
46
+ end
47
+
48
+ def to_s
49
+ tags.join('|')
50
+ end
51
+
52
+ def empty?
53
+ set.empty?
54
+ end
55
+
56
+ def include?(value)
57
+ value = value.to_s.strip
58
+ return false if value.empty?
59
+ set.include?(value)
60
+ end
61
+
62
+ def include_any?(value)
63
+ value.to_a.any? {|tag| self.include?(tag)}
64
+ end
65
+
66
+ def subset_of?(value)
67
+ set.subset?(to_set(value))
68
+ end
69
+
70
+ def superset_of?(value)
71
+ set.superset?(to_set(value))
72
+ end
73
+
74
+ protected
75
+
76
+ def ini_tags
77
+ @ini_tags
78
+ end
79
+
80
+ private
81
+
82
+ def value_to_a(value)
83
+ return value.ini_tags.dup if value.is_a?(self.class)
84
+ return value.dup if value.is_a?(Array)
85
+ return value.to_a if value.is_a?(Set)
86
+ raise ArgumentError, "Expecting #{self.class}, Set or Array. Given: #{value.class}"
87
+ end
88
+
89
+ def to_set(value)
90
+ values = value_to_a(value)
91
+ Set.new.merge(values.compact.map(&:upcase))
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,88 @@
1
+ module Eco::API::UseCases::GraphQL::Helpers::Location
2
+ class TagsRemap
3
+ def self.correct_pair?(pair)
4
+ correct_pair = pair.is_a?(Array)
5
+ correct_pair && pair.length == 2
6
+ end
7
+
8
+ attr_reader :src_maps
9
+ attr_reader :from, :to
10
+
11
+ def each(&block)
12
+ return to_enum(:each) unless block
13
+ src_maps.each(&block)
14
+ end
15
+
16
+ def to_csv(filename)
17
+ CSV.open(filename, "w") do |fd|
18
+ fd << ["src_tags", "dst_tags"]
19
+ each do |tags_map|
20
+ fd << tags_map.to_csv_row
21
+ end
22
+ end
23
+ end
24
+
25
+ def to_s
26
+ "".tap do |str|
27
+ each.map do |tags_map|
28
+ from, to = tags_map.to_csv_row
29
+ str << " • from: #{from}\n"
30
+ str << " • to: #{to}\n"
31
+ end
32
+ end
33
+ end
34
+
35
+ # Sorts the maps in the correct order
36
+ # @note it assumes that:
37
+ # 1. renames go before moving nodes
38
+ # 2. moving nodes does not include a rename
39
+ # 3. moving nodes includes all the path on both, source and destination tags
40
+ # @note DISABLED: the order should be that in what the operations happened!
41
+ # An important thing is that all the path is always included in the mappings.
42
+ # * The only way that this would work is to apply upper (previous) tags_maps
43
+ # to subsequent ones. But the order should still be kept!
44
+ # def sorted_maps
45
+ # [].tap do |sorted|
46
+ # src_maps.each do |curr_map|
47
+ # pos = nil
48
+ # sorted.each_with_index.reverse_each do |prev_map, idx|
49
+ # pos = idx + 1 if prev_map.goes_before?(curr_map)
50
+ # break if pos
51
+ # end
52
+ # sorted.insert(pos || 0, curr_map)
53
+ # end
54
+ # end
55
+ # end
56
+
57
+ def empty?
58
+ src_maps.empty?
59
+ end
60
+
61
+ def <<(pair)
62
+ raise ArgumentError, "Expecting pair of Array in Array. Given: #{pair}" unless self.class.correct_pair?(pair)
63
+ add(*pair)
64
+ end
65
+
66
+ def add(from, to)
67
+ raise ArgumentError, "Expecting Array. Given: #{from.class}" unless from.is_a?(Array)
68
+ raise ArgumentError, "Expecting Array. Given: #{to.class}" unless to.is_a?(Array)
69
+ new_src TagsMap.new(from, to)
70
+ end
71
+
72
+ private
73
+
74
+ attr_reader :src_maps
75
+ attr_reader :from_key, :to_key
76
+
77
+ def new_src(tags_map)
78
+ src_maps << tags_map
79
+ end
80
+
81
+ def src_maps
82
+ @src_maps ||= []
83
+ end
84
+ end
85
+ end
86
+
87
+ require_relative 'tags_remap/tags_map'
88
+ require_relative 'tags_remap/tags_set'
@@ -4,4 +4,5 @@ module Eco::API::UseCases::GraphQL::Helpers
4
4
  end
5
5
 
6
6
  require_relative 'location/base'
7
+ require_relative 'location/tags_remap'
7
8
  require_relative 'location/command'
@@ -18,6 +18,10 @@ class Eco::API::UseCases::GraphQL::Samples::Location
18
18
  Eco::API::UseCases::GraphQL::Helpers::Location::Command::Results
19
19
  end
20
20
 
21
+ def tags_remap_class
22
+ Eco::API::UseCases::GraphQL::Helpers::Location::TagsRemap
23
+ end
24
+
21
25
  # Capture results
22
26
  def results
23
27
  @results ||= {}
@@ -26,7 +30,7 @@ class Eco::API::UseCases::GraphQL::Samples::Location
26
30
  # The maps of tags to be used in batch remap tags
27
31
  # @return [Array<Array>] source/destination pairs of `Array<String>`
28
32
  def tags_remap_table
29
- @tags_remap_table ||= []
33
+ @tags_remap_table ||= tags_remap_class.new
30
34
  end
31
35
 
32
36
  # Errors tracking/logging.
@@ -87,17 +91,17 @@ class Eco::API::UseCases::GraphQL::Samples::Location
87
91
  node_id, parent_id = result.command_input_data.values_at(:nodeId, :parentId)
88
92
  prev_node = previous_tree.node(node_id)
89
93
  curr_node = current_tree.node(node_id)
90
- lost_tags = prev_node.path - curr_node.path
91
- new_tags = curr_node.path - prev_node.path
94
+ prev_path = prev_node.path.reverse
95
+ new_path = curr_node.path.reverse
92
96
 
93
97
  curr_parent = curr_node.parent.top? ? nil : curr_node.parent
94
98
  unless curr_parent&.id == parent_id
95
- msg = "Node '#{node_id}' was moved uner '#{parent_id}', "
99
+ msg = "Node '#{node_id}' was moved under '#{parent_id}', "
96
100
  msg << "but in current structure has parent '#{curr_parent&.id}'"
97
101
  log(:warn) { msg }
98
102
  end
99
103
 
100
- tags_remap_table << [lost_tags.unshift(node_id), new_tags.unshift(node_id)]
104
+ tags_remap_table << [prev_path, new_path]
101
105
  end
102
106
  end
103
107
  end
@@ -108,8 +112,8 @@ class Eco::API::UseCases::GraphQL::Samples::Location
108
112
  timestamp_file(filename).tap do |file|
109
113
  CSV.open(file, 'w') do |csv|
110
114
  csv << ["source_tags", "destination_tags"]
111
- tags_remap_table.each do |(src_tags, dst_tags)|
112
- csv << [src_tags.join('|'), dst_tags.join('|')]
115
+ tags_remap_table.each do |tags_remap|
116
+ csv << tags_remap.to_csv_row
113
117
  end
114
118
  end
115
119
  log(:info) { "Generated file '#{file}'" }
@@ -28,10 +28,11 @@ class Eco::Data::Locations::NodeLevel
28
28
  msg << "\n Adding missing upper level(s): " + missing_nodes.map(&:raw_tag).pretty_inspect
29
29
  log(:info) { msg }
30
30
 
31
- out.push(*tidy_nodes(missing_nodes, prev_level: prev_level, main: false))
32
- # puts node.actual_level
33
- # pp node.tags_array
34
- level = prev_level + 1
31
+ # The very top missing node (first in list) should be checked against prev_level
32
+ # alongside any descendants in missing_nodes (when gap 2+)
33
+ tidied_nodes = tidy_nodes(missing_nodes, prev_level: prev_level, main: false)
34
+ out.push(*tidied_nodes)
35
+ #level = prev_level + 1 # <= we are actually on level and filled in the gaps
35
36
  end
36
37
  out << node
37
38
  done_ids << node_id
@@ -44,6 +45,8 @@ class Eco::Data::Locations::NodeLevel
44
45
  end
45
46
 
46
47
  # Sets the `parentId` property.
48
+ # Although with normalized nodes parents are self-contained
49
+ # we use this method
47
50
  def fill_in_parents(nodes)
48
51
  nodes.tap do |nodes|
49
52
  prev_nodes = empty_level_tracker_hash(11)
@@ -31,7 +31,13 @@ class Eco::Data::Locations::NodeLevel
31
31
  prev_level = nil
32
32
  prev_node = nil
33
33
  prev_nodes = empty_level_tracker_hash(11)
34
-
34
+ prev_node_get = proc do |raw_level|
35
+ prev = nil
36
+ (1..raw_level).to_a.reverse.each do |lev|
37
+ prev ||= prev_nodes[lev]
38
+ end
39
+ prev
40
+ end
35
41
  # Convert to Eco::CSV::Table for a fresh start
36
42
  csv = Eco::CSV.parse(csv.to_csv).nil_blank_cells.add_index_column(:row_num)
37
43
 
@@ -42,10 +48,12 @@ class Eco::Data::Locations::NodeLevel
42
48
 
43
49
  # If node is nested in prev_node or is a sibling thereof
44
50
  if prev_node.raw_level <= node.raw_level
45
- # Make sure parent is among upper level tags
51
+ # Make sure upper level tags are there (including parent)
52
+ # This normalizes input to all upper level tags always filled in
53
+ # which allows to node#actual_level to work
46
54
  node.set_high_levels(prev_node)
47
55
  else
48
- if parent_node = prev_nodes[node.raw_level - 1]
56
+ if parent_node = prev_node_get[node.raw_level - 1]
49
57
  node.set_high_levels(parent_node)
50
58
  elsif node.raw_level == 1
51
59
  # It is expected not to have parent (as it's top level tag)
@@ -47,6 +47,13 @@ module Eco::Data::Locations
47
47
  tags_array.index(raw_tag) + 1
48
48
  end
49
49
 
50
+ def raw_prev_empty_level
51
+ tags_array[0..raw_level-1].each_with_index.reverse_each do |value, idx|
52
+ return idx + 1 unless value
53
+ end
54
+ nil
55
+ end
56
+
50
57
  def tag_idx
51
58
  tags_array.index(raw_tag)
52
59
  end
@@ -106,6 +113,7 @@ module Eco::Data::Locations
106
113
  true
107
114
  end
108
115
 
116
+ # Cleanses deepest tags
109
117
  def override_upper_levels(src_tags_array, from_level: self.raw_level + 1)
110
118
  target_lev = Array(from_level..tag_attrs_count)
111
119
  target_tags = src_tags_array[level_to_idx(from_level)..level_to_idx(tag_attrs_count)]
@@ -115,7 +123,9 @@ module Eco::Data::Locations
115
123
  self
116
124
  end
117
125
 
118
- def override_lower_levels(src_tags_array, to_level: self.raw_level - 1)
126
+ # Ensures parent is among the upper level tags
127
+ def override_lower_levels(src_tags_array, to_level: self.raw_prev_empty_level)
128
+ return self unless to_level
119
129
  target_lev = Array(1..to_level)
120
130
  target_tags = src_tags_array[level_to_idx(1)..level_to_idx(to_level)]
121
131
  target_lev.zip(target_tags).each do |(n, tag)|
data/lib/eco/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Eco
2
- VERSION = "2.5.4"
2
+ VERSION = "2.5.6"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eco-helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.4
4
+ version: 2.5.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oscar Segura
@@ -156,7 +156,7 @@ dependencies:
156
156
  requirements:
157
157
  - - ">="
158
158
  - !ruby/object:Gem::Version
159
- version: 0.3.8
159
+ version: 0.3.10
160
160
  - - "<"
161
161
  - !ruby/object:Gem::Version
162
162
  version: '0.4'
@@ -166,7 +166,7 @@ dependencies:
166
166
  requirements:
167
167
  - - ">="
168
168
  - !ruby/object:Gem::Version
169
- version: 0.3.8
169
+ version: 0.3.10
170
170
  - - "<"
171
171
  - !ruby/object:Gem::Version
172
172
  version: '0.4'
@@ -598,6 +598,9 @@ files:
598
598
  - lib/eco/api/usecases/graphql/helpers/location/command.rb
599
599
  - lib/eco/api/usecases/graphql/helpers/location/command/result.rb
600
600
  - lib/eco/api/usecases/graphql/helpers/location/command/results.rb
601
+ - lib/eco/api/usecases/graphql/helpers/location/tags_remap.rb
602
+ - lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_map.rb
603
+ - lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_set.rb
601
604
  - lib/eco/api/usecases/graphql/samples.rb
602
605
  - lib/eco/api/usecases/graphql/samples/location.rb
603
606
  - lib/eco/api/usecases/graphql/samples/location/command.rb