eco-helpers 2.5.3 → 2.5.5

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: adb24d7f2036b028bdd81302479211ea745e82abdb6b3a69afa2253e3f8fe444
4
- data.tar.gz: 4d149fa100a5a661e4b019aa601c0da3af051ba6e019c77e22b6599c312a4fcb
3
+ metadata.gz: d241b7bc57bd81e9b3a6b1d71ecab5f7530b7b6df176301adc21584722f2b247
4
+ data.tar.gz: a8beafa1e6cd1e3425291121c55b78fe144f155021ab149148fcc6ce35569984
5
5
  SHA512:
6
- metadata.gz: 8583dc7689de13744421db380f3193072f754091a1db6434010568907ff2f4e39d5ed3146b045ab47632cdf0bc5bc8c7e3fe326be466e7b4926f8c8c41344b31
7
- data.tar.gz: b34c886f7cde59854838a087fcb10442f14835a87bf3a264534a4a4641a3a1ca2b302352a920f37536cb0686a213ce7d2d06949c2c2bc89e31f3c8e7650ab32e
6
+ metadata.gz: 2bad589998061077bac0f6adb500fdbb9402b77578da68bf43a84f83101a2bf862213acb00707a363f5583c47ec94a876c367360c562043865e573232b272c4c
7
+ data.tar.gz: 7c33f358f5cbb7ab0b3118d7b0b20e86a9f686f2e1f49216e23bea5ab504ca4bb0bbb2eeab8bb37daf0d11b997af0160212ff7ab2d1fe382fa05a6b354529f48
data/CHANGELOG.md CHANGED
@@ -1,7 +1,37 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
- ## [2.5.3] - 2023-07-xx
4
+ ## [2.5.6] - 2023-08-xx
5
+
6
+ ### Added
7
+ ### Changed
8
+ ### Fixed
9
+
10
+ ## [2.5.5] - 2023-08-03
11
+
12
+ ### Added
13
+ - `Eco::API::Organization::TagTree` - **added/improved methods**
14
+ - `#as_json` new parameter `max_depth:` to be able to cut the tree
15
+ - `#active_tree` new method to exclude archived nodes.
16
+ - `#truncate` new method to obtain a tree cut to `max_depth:`
17
+ - `Eco::API::Session::Config::Tagtree`: **exposed** `include_archived:` (a.k.a. `inludeArchivedNodes`).
18
+ - This change states that `session.tagtree` does not retrieve archived nodes by default, while `session.live_tree` does retrieve archived nodes.
19
+ - This change required an update on the `ecoportal-api-graphql` **gem**
20
+
21
+ ### Fixed
22
+ - `Eco::API::Organization::TagTree#path` - dups the result (rather than exposing the internal path array)
23
+
24
+ ## [2.5.4] - 2023-07-27
25
+
26
+ ### Added
27
+ - SFTP case helpers: `Eco::API::UseCases::GraphQL::Utils::Sftp`
28
+ - `Eco::Data::Locations::NodeDiff` and `Eco::Data::Locations::NodeDiff::NodeDiffs`
29
+ - Aim to identify changes in the locations structure
30
+
31
+ ### Changed
32
+ - Some internal tidy up in `Eco::API::UseCases::GraphQL`
33
+
34
+ ## [2.5.3] - 2023-07-19
5
35
 
6
36
  ### Added
7
37
  - _GraphQL base case_ for **locations structure update**.
@@ -52,7 +82,6 @@ All notable changes to this project will be documented in this file.
52
82
  - `Eco::API::Session::Config::Workflow#exit_handle`
53
83
  - Allows to define a callback on `SystemExit` (`exit` call).
54
84
 
55
- ### Changed
56
85
  ### Fixed
57
86
  - `Eco::API::Session::Config::Workflow` on `SystemExit` preserve original exit `status` value (i.e. `0`, `1`)
58
87
  - It was changing an `exit 1` to be an `exit 0`
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.9', '< 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
@@ -180,8 +209,8 @@ module Eco
180
209
 
181
210
  # Returns a plain list form of hash nodes.
182
211
  # @return [Array[Hash]] where `Hash` is a plain `node`
183
- def as_nodes_json
184
- all_nodes.map {|nd| nd.as_json(include_children: false)}
212
+ def as_nodes_json(&block)
213
+ all_nodes.map {|nd| nd.as_json(include_children: false, &block)}
185
214
  end
186
215
 
187
216
  # @return [Boolean] `true` if there are tags in the node, `false` otherwise.
@@ -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,30 @@ 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
10
15
  if tree_file = self.file
11
16
  if (tree = file_manager.load_json(tree_file)) && !tree.empty?
12
17
  @tagtree = Eco::API::Organization::TagTree.new(tree, enviro: enviro)
13
18
  end
14
19
  end
15
- @tagtree ||= live_tree(enviro: enviro).tap do |tree|
20
+
21
+ kargs = {
22
+ enviro: enviro,
23
+ includeArchivedNodes: include_archived
24
+ }
25
+
26
+ @tagtree ||= live_tree(**kargs).tap do |tree|
16
27
  unless tree && !tree.empty?
17
- raise "Could not find a locations structure."
28
+ msg = "Could not find a local or live locations structure."
29
+ raise MissingTagtree, msg
18
30
  end
19
31
  end
20
32
  end
@@ -23,13 +35,18 @@ module Eco
23
35
  # If `id` is provided, it only retrieves this locations structure.
24
36
  def live_tree(id: nil, enviro: nil, include_archived: false, **kargs, &block)
25
37
  return @live_tree if instance_variable_defined?(:@live_tree) && @live_tree.enviro == enviro
38
+
39
+ kargs = {
40
+ enviro: enviro,
41
+ includeArchivedNodes: include_archived
42
+ }.merge(kargs)
43
+
26
44
  if id
27
- args = {id: id, enviro: enviro, include_archived: include_archived}.merge(kargs)
45
+ args = { id: id }.merge(kargs)
28
46
  @live_tree = live_tree_get(**args, &block)
29
47
  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)
48
+ kargs
49
+ trees = live_trees(**kargs, &block)
33
50
  @live_tree = trees.reject do |tree|
34
51
  tree.empty?
35
52
  end.max do |a,b|
@@ -45,26 +62,33 @@ module Eco
45
62
 
46
63
  # Gets a single locations structure
47
64
  # @note it does not memoize
65
+ # @param include_archived [Boolean] whether or not to include archived **nodes**
66
+ # @return [Eco::API::Organization::TagTree, NilClass]
48
67
  def live_tree_get(id: nil, enviro: nil, include_archived: false, **kargs, &block)
49
68
  return nil unless apis.active_api.version_available?(:graphql)
50
69
  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)
70
+
71
+ kargs = {
72
+ id: id,
73
+ includeArchivedNodes: include_archived
74
+ }.merge(kargs)
75
+
76
+ return nil unless tree = graphql.currentOrganization.locationStructure(**kargs, &block)
55
77
  args = { enviro: enviro, id: tree.id, name: tree.name }
56
78
  Eco::API::Organization::TagTree.new(tree.treeify, **args)
57
79
  end
58
80
 
59
81
  # Retrieves all the location structures of the organisation
82
+ # @param include_archived [Boolean] whether or not to include archived **nodes**
83
+ # @return [Array<Eco::API::Organization::TagTree>]
60
84
  def live_trees(enviro: nil, include_archived: false, **kargs, &block)
61
85
  [].tap do |eco_trees|
62
86
  next unless apis.active_api.version_available?(:graphql)
63
87
  next unless graphql = apis.api(version: :graphql)
88
+
64
89
  kargs = {
65
- includeArchived: include_archived,
66
- includeUnpublished: false
67
- }.merge(kargs).slice(:includeArchived, :includeUnpublished)
90
+ includeArchivedNodes: include_archived
91
+ }.merge(kargs)
68
92
 
69
93
  next unless trees = graphql.currentOrganization.locationStructures(**kargs, &block)
70
94
  trees.each do |tree|
@@ -235,9 +235,15 @@ 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
@@ -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
@@ -0,0 +1,15 @@
1
+ module Eco::API::UseCases::GraphQL::Helpers::Base
2
+ # Basic stuff you would need in any use case
3
+ module CaseEnv
4
+ include Eco::Language::AuxiliarLogger
5
+ attr_reader :session, :options
6
+
7
+ def config
8
+ session.config
9
+ end
10
+
11
+ def simulate?
12
+ options.dig(:simulate)
13
+ end
14
+ end
15
+ end
@@ -1,20 +1,12 @@
1
1
  module Eco::API::UseCases::GraphQL::Helpers
2
2
  module Base
3
- include Eco::Language::AuxiliarLogger
4
- attr_reader :session, :options
3
+ require_relative 'base/case_env'
4
+ include Eco::API::UseCases::GraphQL::Helpers::Base::CaseEnv
5
5
 
6
6
  def graphql
7
7
  @graphql ||= session.api(version: :graphql)
8
8
  end
9
9
 
10
- def config
11
- session.config
12
- end
13
-
14
- def simulate?
15
- options.dig(:simulate)
16
- end
17
-
18
10
  # Keep a copy of the requests/responses for future reference
19
11
  def backup(data, type:)
20
12
  dry_run = simulate? ? "_dry_run" : ""
@@ -24,7 +16,7 @@ module Eco::API::UseCases::GraphQL::Helpers
24
16
  end
25
17
 
26
18
  def exit_error(msg)
27
- logger.error(msg)
19
+ log(:error) { msg }
28
20
  exit(1)
29
21
  end
30
22
  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
@@ -3,17 +3,25 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
3
3
  include Eco::Language::AuxiliarLogger
4
4
  include Eco::API::UseCases::GraphQL::Helpers::Location::Base
5
5
 
6
- # Prevents each request from timing out
7
- COMMANDS_PER_PAGE = 45
8
- # Whether to stop or continue on command fail
9
- FORCE_CONTINUE = false
6
+ DEFAULT_COMMANDS_PER_PAGE = 45
7
+ DEFAULT_FORCE_CONTINUE = false
10
8
 
9
+ # Prevents each request from timing out
11
10
  def commands_per_page
12
- self.class::COMMANDS_PER_PAGE
11
+ if self.class.const_defined?(:COMMANDS_PER_PAGE)
12
+ self.class::COMMANDS_PER_PAGE
13
+ else
14
+ DEFAULT_COMMANDS_PER_PAGE
15
+ end
13
16
  end
14
17
 
18
+ # Whether to stop or continue on command fail
15
19
  def force_continue?
16
- self.class::FORCE_CONTINUE
20
+ if self.class.const_defined?(:FORCE_CONTINUE)
21
+ self.class::FORCE_CONTINUE
22
+ else
23
+ DEFAULT_FORCE_CONTINUE
24
+ end
17
25
  end
18
26
 
19
27
  # With given the commands, it generates the input of the endpoint mutation.
@@ -0,0 +1,74 @@
1
+ module Eco::API::UseCases::GraphQL::Utils
2
+ module Sftp
3
+ include Eco::API::UseCases::GraphQL::Helpers::Base::CaseEnv
4
+
5
+
6
+ def remote_subfolder
7
+ nil
8
+ end
9
+
10
+ def remote_folder(subfolder = remote_subfolder)
11
+ "#{sftp_config.remote_folder}/#{subfolder || ''}"
12
+ end
13
+
14
+ def sftp_group_id
15
+ if self.class.const_defined?(:SFTP_GROUP)
16
+ self.class.const_get(:TARGET_STRUCTURE_ID)
17
+ elsif group_id = options.dig(:sftp, :group)
18
+ group_id
19
+ end
20
+ end
21
+
22
+ def upload(local_file, remote_folder: self.remote_folder, gid: sftp_group_id)
23
+ return false unless local_file && File.exists?(local_file)
24
+ dest_file = "#{remote_folder}/#{File.basename(local_file)}"
25
+ res = sftp_session.upload!(local_file, dest_file)
26
+ attrs = sftp_session.stat!(dest_file)
27
+ if gid && gid != attrs.gid
28
+ stat_res = sftp_session.setstat!(dest_file, {permissions: 0660, uid: attrs.uid, gid: gid})
29
+ end
30
+ logger.info("Uploaded '#{local_file}' (#{res})")
31
+ end
32
+
33
+ def ensure_remote_empty
34
+ files = with_remote_files
35
+ unless files.empty?
36
+ msg = "There are still files in the remote folder that will be deleted: '#{remote_folder}':\n"
37
+ msg += " • " + files.map do |file|
38
+ file.longname
39
+ end.join("\n • ") + "\n"
40
+ session.prompt_user("Do you want to proceed to delete? (Y/n):", explanation: msg, default: "Y", timeout: 3) do |response|
41
+ if response.upcase.start_with?("Y")
42
+ files.each do |file|
43
+ remote_full_path = to_remote_path(file.name)
44
+ res = sftp_session.remove(remote_full_path)
45
+ logger.info("Deleted remote file: '#{remote_full_path}' (#{res})")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def with_remote_files
53
+ sftp.files(remote_folder).each do |remote_file|
54
+ yield(remote_file) if block_given?
55
+ end
56
+ end
57
+
58
+ def to_remote_path(file, subfolder: nil)
59
+ remote_folder(subfolder) + "/" + file
60
+ end
61
+
62
+ def sftp_config
63
+ session.config.sftp
64
+ end
65
+
66
+ def sftp_session
67
+ sftp.sftp_session
68
+ end
69
+
70
+ def sftp
71
+ session.sftp
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,6 @@
1
+ module Eco::API::UseCases::GraphQL
2
+ module Utils
3
+ end
4
+ end
5
+
6
+ require_relative 'utils/sftp'
@@ -8,5 +8,6 @@ module Eco
8
8
  end
9
9
 
10
10
  require_relative 'graphql/helpers'
11
+ require_relative 'graphql/utils'
11
12
  require_relative 'graphql/base'
12
13
  require_relative 'graphql/samples'
@@ -75,11 +75,11 @@ module Eco
75
75
  Dir.exist?(path) || Dir.exist?(File.expand_path(path))
76
76
  end
77
77
 
78
- def timestamp(timestamp_pattern = DEFAULT_TIMESTAMP_PATTERN)
79
- Time.now.strftime(timestamp_pattern)
78
+ def timestamp(timestamp_pattern = DEFAULT_TIMESTAMP_PATTERN, date = Time.now)
79
+ date.strftime(timestamp_pattern)
80
80
  end
81
81
 
82
- def timestamp_file(filename, timestamp_pattern = DEFAULT_TIMESTAMP_PATTERN)
82
+ def timestamp_file(filename, timestamp_pattern =DEFAULT_TIMESTAMP_PATTERN)
83
83
  file_pattern = Eco::Data::Files::FilePattern.new(filename)
84
84
  file_pattern.resolve(start: timestamp(timestamp_pattern) + '_')
85
85
  end
@@ -3,47 +3,16 @@ module Eco
3
3
  module Hashes
4
4
  class ArrayDiff
5
5
  extend Eco::Language::Models::ClassHelpers
6
-
7
- class << self
8
- def key(value = nil)
9
- return @key unless value
10
- @key = value.to_s
11
- end
12
-
13
- def key?
14
- !!@key
15
- end
16
-
17
- def compare(*attrs)
18
- compared_attrs.push(*attrs.map(&:to_s)).uniq!
19
- end
20
-
21
- def case_sensitive(value = nil)
22
- @case_sensitive = false unless instance_variable_defined?(:@case_sensitive)
23
- return @case_sensitive unless value
24
- @case_sensitive = !!value
25
- end
26
-
27
- def case_sensitive?
28
- !!@case_sensitive
29
- end
30
-
31
- def compared_attrs
32
- @compared_attrs ||= []
33
- @compared_attrs
34
- end
35
- end
6
+ # We can change the `DiffResult` class (items)
7
+ class_resolver :diff_result_class, "Eco::Data::Hash::DiffResult"
36
8
 
37
9
  include Eco::Language::AuxiliarLogger
38
10
 
39
11
  attr_reader :source1, :source2
40
12
  attr_reader :src_h1, :src_h2
41
13
 
42
- class_resolver :diff_result_class, "Eco::Data::Hash::DiffResult"
43
-
44
- def initialize(source1, source2, logger: nil, **kargs)
14
+ def initialize(source1, source2, logger: nil)
45
15
  @logger = logger if logger
46
- @options = kargs
47
16
  @source1 = source1
48
17
  @source2 = source2
49
18
  @src_h1 = by_key(source1)
@@ -67,14 +36,11 @@ module Eco
67
36
  !diffs.empty?
68
37
  end
69
38
 
39
+ # All the items that contain the diff of a node.
40
+ # @return [Array<Eco::Data::Hash::DiffResult>]
70
41
  def source_results
71
42
  @source_results ||= paired_sources.each_with_object([]) do |(src1, src2), res|
72
- args = {
73
- key: key,
74
- compare: compared_attrs,
75
- case_sensitive: case_sensitive?
76
- }
77
- res << diff_result_class.new(src1, src2, **args)
43
+ res << diff_result_class.new(src1, src2)
78
44
  end
79
45
  end
80
46
 
@@ -91,35 +57,23 @@ module Eco
91
57
  all_keys.map {|key| [src_h1[key], src_h2[key]]}
92
58
  end
93
59
 
60
+ # @return [String] the `key` attribute of `diff_result_class`
94
61
  def key
95
- @key ||= options_or(:key) do
96
- self.class.key
97
- end.tap do |k|
98
- raise "missing main key attr to pair records. Given: #{k}" unless k.is_a?(String)
62
+ diff_result_class.key.tap do |k|
63
+ raise "#{diff_result_class}: missing main key attr to pair records. Given: #{k}" unless k.is_a?(String)
99
64
  end
100
65
  end
101
66
 
102
67
  def case_sensitive?
103
- @case_sensitive ||= options_or(:case_sensitive) { self.class.case_sensitive? }
68
+ diff_result_class.case_sensitive?
104
69
  end
105
70
 
106
71
  def compared_attrs
107
- @compared_attrs ||= options_or(:compared_attrs) do
108
- self.class.compared_attrs
109
- end.yield_self do |attrs|
110
- raise "compared_attrs should be an array" unless attrs.is_a?(Array)
111
- attrs.map(&:to_s)
112
- end
72
+ diff_result_class.compared_attrs.map(&:to_s)
113
73
  end
114
74
 
115
75
  private
116
76
 
117
- def options_or(opt)
118
- opt = opt.to_sym
119
- return @options[opt] if @options.key?(opt)
120
- yield
121
- end
122
-
123
77
  def symbolize_keys(hash)
124
78
  hash.each_with_object({}) do |(k, v), h|
125
79
  h[k.to_sym] = v
@@ -0,0 +1,52 @@
1
+ module Eco
2
+ module Data
3
+ module Hashes
4
+ module DiffMeta
5
+ class << self
6
+ def included(base)
7
+ super(base)
8
+ base.extend ClassMethods
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ # @param value [String, NilClass]
14
+ # @return [String] the attribute that is key of the node diff elements.
15
+ def key(value = nil)
16
+ return @key unless value
17
+ @key = value.to_s
18
+ end
19
+
20
+ # @return [Boolean] is there a `key` attribute defined?
21
+ def key?
22
+ !!@key
23
+ end
24
+
25
+ # @param attrs [Array<Symbol>, Array<String>]
26
+ # @return [Array<String>] the comparable attributes
27
+ def compare(*attrs)
28
+ compared_attrs.push(*attrs.map(&:to_s)).uniq!
29
+ compared_attrs
30
+ end
31
+
32
+ # Whether or not the diff calc of a node should be done case sensitive or insensitive.
33
+ def case_sensitive(value = nil)
34
+ @case_sensitive = false unless instance_variable_defined?(:@case_sensitive)
35
+ return @case_sensitive unless value
36
+ @case_sensitive = !!value
37
+ end
38
+
39
+ # @return [Boolean] are comparisons of values done case sensitive?
40
+ def case_sensitive?
41
+ !!@case_sensitive
42
+ end
43
+
44
+ # @return [Array<String>] the comparable attributes
45
+ def compared_attrs
46
+ @compared_attrs ||= []
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -2,19 +2,32 @@ module Eco
2
2
  module Data
3
3
  module Hashes
4
4
  class DiffResult
5
- attr_reader :key
5
+ extend Eco::Language::Models::ClassHelpers
6
+ include Eco::Data::Hashes::DiffMeta
7
+
8
+ inheritable_class_vars :key, :compared_attrs, :case_sensitive
9
+
6
10
  attr_reader :src1, :src2
7
11
 
8
- # @param compare [Array<String>, sym]
9
- # - `:all` compares the matching attrs between both hashes only
10
- def initialize(src1, src2, key:, compare: :all, case_sensitive: false)
11
- @key = key
12
- @compare = compare
13
- @case_sensitive = case_sensitive
12
+ def initialize(src1, src2)
14
13
  @src1 = src1
15
14
  @src2 = src2
16
15
  end
17
16
 
17
+ def key
18
+ self.class.key
19
+ end
20
+
21
+ def case_sensitive?
22
+ self.class.case_sensitive?
23
+ end
24
+
25
+ def dup(src1: nil, src2: nil)
26
+ src1 ||= self.src1
27
+ src2 ||= self.src2
28
+ self.class.new(src1.dup, src2.dup)
29
+ end
30
+
18
31
  def new?
19
32
  !src1 && !!src2
20
33
  end
@@ -27,35 +40,39 @@ module Eco
27
40
  !new? && !del? && diff?
28
41
  end
29
42
 
43
+ # @note `diff_attrs` may not include the `key` attribute
44
+ # This is always included via `new?` (new key value) and `del?` (missing key value)
45
+ # @return [Boolean] was there any change?
30
46
  def diff?
31
47
  new? || del? || !diff_attrs.empty?
32
48
  end
33
49
 
34
- # Is the key attr value changing?
50
+ # Is the `key` attr value changing?
35
51
  def key?
36
52
  !(new? || del?) && diff_attr?(key)
37
53
  end
38
54
 
55
+ # Is `attr` part of the attributes that change?
39
56
  def diff_attr?(attr)
40
57
  return true if new?
41
58
  return true if del?
42
59
  diff_attrs.include?(attr.to_s)
43
60
  end
44
61
 
62
+ # @return [Value] the current value of `attr` (in `src2`)
45
63
  def attr(attr)
46
64
  return nil unless src2
47
65
  src2[attr.to_s]
48
66
  end
49
67
 
68
+ # @return [Value] the previous value of `attr` (in `src1`)
50
69
  def attr_prev(attr)
51
70
  return nil unless src1
52
71
  src1[attr.to_s]
53
72
  end
54
73
 
55
- def previous(attr)
56
- src1 && src1[attr.to_s]
57
- end
58
-
74
+ # @note the `key` attribute will always be added (even if there's no change)
75
+ # @return [Hash] hash with the differences as per `src2`
59
76
  def diff_hash
60
77
  target_attrs = [key] | compared_attrs
61
78
  return src2.slice(*target_attrs) if new?
@@ -63,12 +80,14 @@ module Eco
63
80
  src2.slice(key, *diff_attrs)
64
81
  end
65
82
 
83
+ # @return [Array<Symbol>] hash with the differences as per `src2`
66
84
  def diff_attrs
67
85
  @diff_attrs ||= comparable_attrs.each_with_object([]) do |attr, out|
68
86
  out << attr unless eq?(src1[attr], src2[attr])
69
87
  end
70
88
  end
71
89
 
90
+ # @return [Boolean] whether `val1` is equal to `val2`
72
91
  def eq?(val1, val2)
73
92
  return true if val1 == val2
74
93
  return false if case_sensitive?
@@ -76,25 +95,18 @@ module Eco
76
95
  val1.upcase == val2.upcase
77
96
  end
78
97
 
79
- def case_sensitive?
80
- !!@case_sensitive
81
- end
82
-
98
+ # @note when is `new?` or to be deleted (`del?`), it returns empty array.
99
+ # @return [Array<String>] the set of attributes that are comparable in this instance.
83
100
  def comparable_attrs
84
101
  return [] if new? || del?
85
102
  compared_attrs
86
103
  end
87
104
 
105
+ # @return [Array<String>] the set of attributes that are comparable in this class.
88
106
  def compared_attrs
89
- return @compared_attrs if instance_variable_defined?(:@compared_attrs)
90
- @compared_attrs = \
91
- if @compare == :all
92
- src1.keys & src2.keys
93
- elsif @compare.is_a?(Array)
94
- @compare.map(&:to_s)
95
- else
96
- raise "Expecting 'compare' to be sym (:all) or Array<String>. Given: #{@compare.class}"
97
- end
107
+ comp_attrs = self.class.compared_attrs.map(&:to_s).uniq
108
+ return comp_attrs unless comp_attrs.empty?
109
+ (src1&.keys || []) & (src2&.keys || [])
98
110
  end
99
111
  end
100
112
  end
@@ -5,5 +5,6 @@ module Eco
5
5
  end
6
6
  end
7
7
 
8
+ require_relative 'hashes/diff_meta'
8
9
  require_relative 'hashes/diff_result'
9
10
  require_relative 'hashes/array_diff'
@@ -0,0 +1,46 @@
1
+ class Eco::Data::Locations::NodeDiff
2
+ module Accessors
3
+ class << self
4
+ def included(base)
5
+ super(base)
6
+ base.extend Eco::Language::Models::ClassHelpers
7
+ base.extend ClassMethods
8
+ base.inheritable_class_vars :exposed_attrs
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ # Creates the defined accessor attributes against `NodeDiff`
14
+ # It also creates a method with question mark that evaluates true if **any** the attr changed.
15
+ # @note the defined attributes are expected to be the keys within
16
+ # the source Hashes that are being compared.
17
+ # @note accessing `src1` (prev) attributes, will have it's method as `prev_[attrName]`
18
+ def attr_expose(*attrs)
19
+ attrs.each do |attr|
20
+ meth = attr.to_sym
21
+ methp = "prev_#{meth}".to_sym
22
+ methq = "diff_#{meth}?".to_sym
23
+
24
+ define_method meth do
25
+ attr(meth)
26
+ end
27
+
28
+ define_method methp do
29
+ attr_prev(meth)
30
+ end
31
+
32
+ exposed_attrs |= [meth]
33
+
34
+ define_method methq do
35
+ diff_attr?(meth)
36
+ end
37
+ end
38
+ end
39
+
40
+ # Keeps track on what attributes have been exposed.
41
+ def exposed_attrs
42
+ @exposed_attrs ||= []
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,90 @@
1
+ class Eco::Data::Locations::NodeDiff
2
+ # Adjusts ArrayDiff for Nodes diffs in a location structure
3
+ class NodesDiff < Eco::Data::Hashes::ArrayDiff
4
+ extend Eco::Data::Locations::NodeDiff::Selectors
5
+
6
+ SELECTORS = %i[id name id_name insert unarchive update move archive]
7
+ selector *SELECTORS
8
+
9
+ class_resolver :diff_result_class, Eco::Data::Locations::NodeDiff
10
+ attr_reader :original_tree
11
+
12
+ def initialize(*args, original_tree:, **kargs, &block)
13
+ super(*args, **kargs, &block)
14
+ @original_tree = original_tree
15
+ end
16
+
17
+ def diffs
18
+ @diffs ||= super.select do |res|
19
+ res.unarchive? || res.id_name? || res.insert? || res.move? || res.archive?
20
+ end
21
+ end
22
+
23
+ def diffs_details
24
+ section = '#' * 10
25
+ msg = ''
26
+ if insert?
27
+ msg << " #{section} I N S E R T S #{section}\n"
28
+ msg << insert.map {|d| d.diff_hash.pretty_inspect}.join('')
29
+ end
30
+
31
+ if update?
32
+ msg << "\n #{section} U P D A T E S #{section}\n"
33
+ update.each do |d|
34
+ flags = ''
35
+ #flags << 'i' if d.id?
36
+ flags << 'n' if d.diff_name?
37
+ flags << 'm' if d.move?
38
+ flags << 'u' if d.unarchive?
39
+ msg << "<< #{flags} >> "
40
+ msg << d.diff_hash.pretty_inspect
41
+ end
42
+ end
43
+
44
+ if archive?
45
+ msg << "\n #{section} A R C H I V E S #{section}\n"
46
+ msg << archive.map {|d| d.diff_hash.pretty_inspect}.join('')
47
+ end
48
+
49
+ msg
50
+ end
51
+
52
+ def diffs_summary
53
+ return "There were no differences identified" if diffs.empty?
54
+ msg = "Identified #{diffs.count} differences:\n"
55
+ msg << when_present(insert, '') do |count|
56
+ " • #{count} nodes to insert\n"
57
+ end
58
+ msg << when_present(update, '') do |count|
59
+ " • #{count} nodes to update\n"
60
+ end
61
+ # msg << when_present(id, '') do |count|
62
+ # " • #{count} nodes to change id\n"
63
+ # end
64
+ msg << when_present(name, '') do |count|
65
+ " • #{count} nodes to change name\n"
66
+ end
67
+ msg << when_present(move, '') do |count|
68
+ " • #{count} nodes to move\n"
69
+ end
70
+ msg << when_present(unarchive, '') do |count|
71
+ " • #{count} nodes to unarchive\n"
72
+ end
73
+ msg << when_present(archive, '') do |count|
74
+ " • #{count} nodes to archive\n"
75
+ end
76
+ msg
77
+ end
78
+
79
+ private
80
+
81
+ def when_present(list, default = nil)
82
+ count = list.count
83
+ if count > 0
84
+ yield(count)
85
+ else
86
+ default
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,20 @@
1
+ class Eco::Data::Locations::NodeDiff
2
+ module Selectors
3
+ # Creates selector methods on `diffs` (nodes that changed)
4
+ # It also creates a method with question mark that evaluates true if **any** diff matches.
5
+ # @note the selector method name with a question mark should exist in the `diff_result_class`
6
+ def selector(*attrs)
7
+ attrs.each do |attr|
8
+ meth = attr.to_sym
9
+ methq = "#{meth}?".to_sym
10
+ define_method meth do
11
+ diffs.select(&methq)
12
+ end
13
+
14
+ define_method methq do
15
+ diffs.any(&methq)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,55 @@
1
+ module Eco::Data::Locations
2
+ # Differences between node before and after
3
+ # @note this works with `Hash` object where basic keys are:
4
+ # - `nodeId`
5
+ # - `name`
6
+ # - `parentId`
7
+ # - `archived`
8
+ # @note other properties can be part of the `Hash` although
9
+ # they may not influence the results.
10
+ class NodeDiff < Eco::Data::Hashes::DiffResult
11
+ require_relative 'node_diff/accessors'
12
+ require_relative 'node_diff/selectors'
13
+ require_relative 'node_diff/nodes_diff'
14
+
15
+ include Eco::Data::Locations::NodeDiff::Accessors
16
+
17
+ key :nodeId
18
+ compare :parentId, :name, :archived
19
+ case_sensitive false
20
+
21
+ attr_expose :nodeId, :name, :parentId, :archived
22
+
23
+ alias_method :insert?, :new?
24
+
25
+ alias_method :diff_name_src?, :diff_name?
26
+ # Has the property `name` changed?
27
+ def diff_name?
28
+ diff_name_src? && update?
29
+ end
30
+ alias_method :name?, :diff_name?
31
+ alias_method :id? , :diff_name? # currently a change of name is a change of id (tag)
32
+ #alias_method :id? , :key?
33
+ #alias_method :nodeId? , :id?
34
+
35
+ # Has any of `id` or `name` properties changed?
36
+ def id_name?
37
+ id? || diff_name?
38
+ end
39
+
40
+ # Has the parent id changed?
41
+ def move?
42
+ update? && diff_parentId?
43
+ end
44
+
45
+ # Has the `archived` property changed and it was `true`?
46
+ def unarchive?
47
+ !archived && update? && diff_archived?
48
+ end
49
+
50
+ # Has the `archived` property changed and it was `false`?
51
+ def archive?
52
+ !prev_archived && (del? || archived)
53
+ end
54
+ end
55
+ end
@@ -18,13 +18,10 @@ module Eco::Data::Locations
18
18
 
19
19
  attr_accessor :parentId
20
20
 
21
- def nodeId
22
- id
23
- end
24
-
25
21
  def id
26
22
  tag.upcase
27
23
  end
24
+ alias_method :nodeId, :id
28
25
 
29
26
  def name
30
27
  tag
@@ -17,6 +17,8 @@ module Eco::Data::Locations
17
17
  def id
18
18
  clean_id(super)
19
19
  end
20
+ # backwards compatibility
21
+ alias_method :tag, :id
20
22
 
21
23
  def name
22
24
  super || self.id
@@ -25,10 +27,5 @@ module Eco::Data::Locations
25
27
  def parentId
26
28
  self.parent_id
27
29
  end
28
-
29
- # backwards compatibility
30
- def tag
31
- id
32
- end
33
30
  end
34
31
  end
@@ -9,4 +9,5 @@ require_relative 'locations/convert'
9
9
  require_relative 'locations/node_base'
10
10
  require_relative 'locations/node_plain'
11
11
  require_relative 'locations/node_level'
12
+ require_relative 'locations/node_diff'
12
13
  require_relative 'locations/dsl'
data/lib/eco/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Eco
2
- VERSION = "2.5.3"
2
+ VERSION = "2.5.5"
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.3
4
+ version: 2.5.5
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.9
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.9
170
170
  - - "<"
171
171
  - !ruby/object:Gem::Version
172
172
  version: '0.4'
@@ -592,6 +592,7 @@ files:
592
592
  - lib/eco/api/usecases/graphql/base.rb
593
593
  - lib/eco/api/usecases/graphql/helpers.rb
594
594
  - lib/eco/api/usecases/graphql/helpers/base.rb
595
+ - lib/eco/api/usecases/graphql/helpers/base/case_env.rb
595
596
  - lib/eco/api/usecases/graphql/helpers/location.rb
596
597
  - lib/eco/api/usecases/graphql/helpers/location/base.rb
597
598
  - lib/eco/api/usecases/graphql/helpers/location/command.rb
@@ -603,6 +604,8 @@ files:
603
604
  - lib/eco/api/usecases/graphql/samples/location/command/dsl.rb
604
605
  - lib/eco/api/usecases/graphql/samples/location/command/results.rb
605
606
  - lib/eco/api/usecases/graphql/samples/location/dsl.rb
607
+ - lib/eco/api/usecases/graphql/utils.rb
608
+ - lib/eco/api/usecases/graphql/utils/sftp.rb
606
609
  - lib/eco/api/usecases/ooze_cases.rb
607
610
  - lib/eco/api/usecases/ooze_cases/export_register_case.rb
608
611
  - lib/eco/api/usecases/ooze_samples.rb
@@ -670,6 +673,7 @@ files:
670
673
  - lib/eco/data/fuzzy_match/string_helpers.rb
671
674
  - lib/eco/data/hashes.rb
672
675
  - lib/eco/data/hashes/array_diff.rb
676
+ - lib/eco/data/hashes/diff_meta.rb
673
677
  - lib/eco/data/hashes/diff_result.rb
674
678
  - lib/eco/data/locations.rb
675
679
  - lib/eco/data/locations/convert.rb
@@ -681,6 +685,10 @@ files:
681
685
  - lib/eco/data/locations/node_base/serial.rb
682
686
  - lib/eco/data/locations/node_base/tag_validations.rb
683
687
  - lib/eco/data/locations/node_base/treeify.rb
688
+ - lib/eco/data/locations/node_diff.rb
689
+ - lib/eco/data/locations/node_diff/accessors.rb
690
+ - lib/eco/data/locations/node_diff/nodes_diff.rb
691
+ - lib/eco/data/locations/node_diff/selectors.rb
684
692
  - lib/eco/data/locations/node_level.rb
685
693
  - lib/eco/data/locations/node_level/builder.rb
686
694
  - lib/eco/data/locations/node_level/cleaner.rb