eco-helpers 2.5.3 → 2.5.5

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: 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