eco-helpers 2.5.5 → 2.5.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d241b7bc57bd81e9b3a6b1d71ecab5f7530b7b6df176301adc21584722f2b247
4
- data.tar.gz: a8beafa1e6cd1e3425291121c55b78fe144f155021ab149148fcc6ce35569984
3
+ metadata.gz: ed268fb73bb35106da813f51a7b9469d0ee0fe167cee520ac44f98fed22c622b
4
+ data.tar.gz: c6b6e0f72037f2a1dac9313293317a78a4d25c52568c7577f88efdafb37d7994
5
5
  SHA512:
6
- metadata.gz: 2bad589998061077bac0f6adb500fdbb9402b77578da68bf43a84f83101a2bf862213acb00707a363f5583c47ec94a876c367360c562043865e573232b272c4c
7
- data.tar.gz: 7c33f358f5cbb7ab0b3118d7b0b20e86a9f686f2e1f49216e23bea5ab504ca4bb0bbb2eeab8bb37daf0d11b997af0160212ff7ab2d1fe382fa05a6b354529f48
6
+ metadata.gz: 21d9f1703574866e548daa22f1e196b237a8415fb750fe53bb6c4cd63e5fa90002a9e2d5f90e5f3aaca769f639e6d9c02f40b0e7a7a123a15965af41bff91828
7
+ data.tar.gz: a02e32a2945f0e4e7a5780e340e8ef400e434d00f2cf44d9969d8573bf84e94c5d35c4c0319e205e351837dc016eb9e389759db3d2291ceab493943af597170b
data/CHANGELOG.md CHANGED
@@ -1,12 +1,31 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
- ## [2.5.6] - 2023-08-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
+
10
29
  ## [2.5.5] - 2023-08-03
11
30
 
12
31
  ### 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.9', '< 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'
@@ -12,6 +12,7 @@ module Eco
12
12
  # @return [Eco::API::Organization::TagTree]
13
13
  def scope_tree(enviro: nil, include_archived: true, raise_on_missing: true)
14
14
  return @tagtree if instance_variable_defined?(:@tagtree) && @tagtree.enviro == enviro
15
+
15
16
  if tree_file = self.file
16
17
  if (tree = file_manager.load_json(tree_file)) && !tree.empty?
17
18
  @tagtree = Eco::API::Organization::TagTree.new(tree, enviro: enviro)
@@ -34,7 +35,14 @@ module Eco
34
35
  # Among all the locations structures it selects the one with more location nodes
35
36
  # If `id` is provided, it only retrieves this locations structure.
36
37
  def live_tree(id: nil, enviro: nil, include_archived: false, **kargs, &block)
37
- 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
38
46
 
39
47
  kargs = {
40
48
  enviro: enviro,
@@ -45,7 +53,6 @@ module Eco
45
53
  args = { id: id }.merge(kargs)
46
54
  @live_tree = live_tree_get(**args, &block)
47
55
  else
48
- kargs
49
56
  trees = live_trees(**kargs, &block)
50
57
  @live_tree = trees.reject do |tree|
51
58
  tree.empty?
@@ -54,8 +61,16 @@ module Eco
54
61
  end
55
62
  end.tap do |tree|
56
63
  if tree
57
- msg = "Using LIVE LOCATIONS Structure: '#{tree.name}' (#{tree.count} nodes)"
58
- 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})"
59
74
  end
60
75
  end
61
76
  end
@@ -74,6 +89,7 @@ module Eco
74
89
  }.merge(kargs)
75
90
 
76
91
  return nil unless tree = graphql.currentOrganization.locationStructure(**kargs, &block)
92
+
77
93
  args = { enviro: enviro, id: tree.id, name: tree.name }
78
94
  Eco::API::Organization::TagTree.new(tree.treeify, **args)
79
95
  end
@@ -250,11 +250,7 @@ module Eco
250
250
  # @note it requires graphql connection configuration parameters
251
251
  # @return [Eco::API::Organization::TagTree]
252
252
  def live_tree(id: nil, enviro: nil, **kargs, &block)
253
- if id
254
- tagtree_config.live_tree_get(id: id, enviro: enviro, **kargs, &block)
255
- else
256
- tagtree_config.live_tree(enviro: enviro, **kargs, &block)
257
- end
253
+ tagtree_config.live_tree(id: id, enviro: enviro, **kargs, &block)
258
254
  end
259
255
 
260
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
@@ -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.5"
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.5
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.9
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.9
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