eco-helpers 2.6.4 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +95 -0
- data/CHANGELOG.md +128 -2
- data/Rakefile +13 -7
- data/eco-helpers.gemspec +2 -2
- data/lib/eco/api/common/loaders/base.rb +2 -2
- data/lib/eco/api/common/loaders/case_base.rb +1 -1
- data/lib/eco/api/common/loaders/config/workflow/mailer.rb +5 -5
- data/lib/eco/api/common/loaders/error_handler.rb +8 -5
- data/lib/eco/api/common/loaders/parser.rb +44 -22
- data/lib/eco/api/common/loaders/policy.rb +6 -4
- data/lib/eco/api/common/loaders/use_case.rb +13 -7
- data/lib/eco/api/common/people/base_parser.rb +0 -2
- data/lib/eco/api/common/people/default_parsers/boolean_parser.rb +0 -1
- data/lib/eco/api/common/people/default_parsers/csv_parser.rb +1 -1
- data/lib/eco/api/common/people/default_parsers/date_parser.rb +64 -12
- data/lib/eco/api/common/people/default_parsers/freemium_parser.rb +0 -1
- data/lib/eco/api/common/people/default_parsers/login_providers_parser.rb +13 -5
- data/lib/eco/api/common/people/default_parsers/multi_parser.rb +0 -1
- data/lib/eco/api/common/people/default_parsers/numeric_parser.rb +18 -5
- data/lib/eco/api/common/people/default_parsers/policy_groups_parser.rb +8 -8
- data/lib/eco/api/common/people/default_parsers/select_parser.rb +50 -26
- data/lib/eco/api/common/people/default_parsers/send_invites_parser.rb +6 -6
- data/lib/eco/api/common/people/default_parsers/xls_parser.rb +9 -12
- data/lib/eco/api/common/people/default_parsers.rb +1 -12
- data/lib/eco/api/common/people/entries.rb +13 -13
- data/lib/eco/api/common/people/entry_factory.rb +76 -45
- data/lib/eco/api/common/people/person_attribute_parser.rb +8 -12
- data/lib/eco/api/common/people/person_entry.rb +86 -75
- data/lib/eco/api/common/people/person_entry_attribute_mapper.rb +60 -44
- data/lib/eco/api/common/people/person_factory.rb +30 -22
- data/lib/eco/api/common/people/person_modifier.rb +11 -13
- data/lib/eco/api/common/people/person_parser.rb +101 -39
- data/lib/eco/api/common/people/supervisor_helpers.rb +25 -26
- data/lib/eco/api/common/session/base_session.rb +9 -9
- data/lib/eco/api/common/session/environment.rb +7 -5
- data/lib/eco/api/common/session/sftp.rb +59 -32
- data/lib/eco/api/common/version_patches/exception.rb +11 -13
- data/lib/eco/api/error.rb +32 -20
- data/lib/eco/api/organization/node_classifications.rb +82 -0
- data/lib/eco/api/organization/policy_groups.rb +4 -6
- data/lib/eco/api/organization/tag_tree.rb +169 -93
- data/lib/eco/api/organization.rb +1 -0
- data/lib/eco/api/session/batch/job.rb +1 -1
- data/lib/eco/api/session/config/tagtree.rb +41 -23
- data/lib/eco/api/session/config/workflow.rb +113 -88
- data/lib/eco/api/session/config.rb +6 -0
- data/lib/eco/api/session.rb +51 -29
- data/lib/eco/api/usecases/base_io.rb +28 -25
- data/lib/eco/api/usecases/default/locations/cli/tagtree_extract_cli.rb +7 -2
- data/lib/eco/api/usecases/default/locations/cli/tagtree_upload_cli.rb +21 -0
- data/lib/eco/api/usecases/default/locations/csv_to_tree_case.rb +3 -3
- data/lib/eco/api/usecases/default/locations/tagtree_extract_case.rb +54 -23
- data/lib/eco/api/usecases/default/locations/tagtree_upload_case.rb +87 -0
- data/lib/eco/api/usecases/default/locations.rb +1 -0
- data/lib/eco/api/usecases/default/people/analyse_people_case.rb +60 -56
- data/lib/eco/api/usecases/default/people/change_email_case.rb +8 -9
- data/lib/eco/api/usecases/default/people/clean_unknown_tags_case.rb +13 -11
- data/lib/eco/api/usecases/default/people/clear_abilities_case.rb +2 -2
- data/lib/eco/api/usecases/default/people/org_data_convert_case.rb +25 -27
- data/lib/eco/api/usecases/default/people/refresh_case.rb +2 -2
- data/lib/eco/api/usecases/default/people/reinvite_trans_case.rb +1 -1
- data/lib/eco/api/usecases/default/people/reinvite_trans_cli.rb +0 -1
- data/lib/eco/api/usecases/default/people/restore_db_case.rb +39 -34
- data/lib/eco/api/usecases/default/people/set_default_tag_case.rb +19 -15
- data/lib/eco/api/usecases/default/people/supers_cyclic_identify_case.rb +16 -12
- data/lib/eco/api/usecases/default_cases/hris_case.rb +17 -15
- data/lib/eco/api/usecases/default_cases/samples/sftp_case.rb +30 -16
- data/lib/eco/api/usecases/graphql/base.rb +5 -3
- data/lib/eco/api/usecases/graphql/helpers/base/case_env.rb +4 -1
- data/lib/eco/api/usecases/graphql/helpers/base/graphql_env.rb +14 -0
- data/lib/eco/api/usecases/graphql/helpers/base.rb +5 -4
- data/lib/eco/api/usecases/graphql/helpers/location/base/classifications_parser.rb +60 -0
- data/lib/eco/api/usecases/graphql/helpers/location/base/tree_tracking.rb +72 -0
- data/lib/eco/api/usecases/graphql/helpers/location/base.rb +25 -59
- data/lib/eco/api/usecases/graphql/helpers/location/command/diff/as_update.rb +59 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diff/compare.rb +49 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diff.rb +11 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/commandable.rb +46 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_archive.rb +23 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_unarchive.rb +65 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable.rb +49 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/sortable/relation_safe_sort.rb +119 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/sortable.rb +59 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages.rb +82 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs.rb +20 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/optimizations.rb +84 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/result.rb +4 -4
- data/lib/eco/api/usecases/graphql/helpers/location/command/results.rb +24 -12
- data/lib/eco/api/usecases/graphql/helpers/location/command.rb +21 -24
- data/lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_map.rb +1 -1
- data/lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_set.rb +10 -11
- data/lib/eco/api/usecases/graphql/helpers/location/tags_remap.rb +8 -9
- data/lib/eco/api/usecases/graphql/samples/location/command/dsl.rb +41 -12
- data/lib/eco/api/usecases/graphql/samples/location/command/results.rb +11 -80
- data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +89 -0
- data/lib/eco/api/usecases/graphql/samples/location/command/service.rb +6 -0
- data/lib/eco/api/usecases/graphql/samples/location/command/track_changed_ids.rb +89 -0
- data/lib/eco/api/usecases/graphql/samples/location/command.rb +3 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/base.rb +9 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/heading.rb +18 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/inputable.rb +53 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing/classifications.rb +34 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing/helpers.rb +28 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing.rb +46 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible.rb +38 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff.rb +105 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/discarded.rb +16 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/input.rb +15 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/node_attr_maps.rb +22 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/parser.rb +45 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter.rb +36 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/output.rb +56 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list.rb +41 -0
- data/lib/eco/api/usecases/graphql/samples/location/service.rb +8 -0
- data/lib/eco/api/usecases/graphql/samples/location.rb +1 -0
- data/lib/eco/api/usecases/graphql/utils/sftp.rb +96 -36
- data/lib/eco/api/usecases/ooze_cases/export_register_case.rb +8 -6
- data/lib/eco/api/usecases/ooze_samples/helpers/creatable.rb +4 -3
- data/lib/eco/api/usecases/ooze_samples/helpers/exportable_ooze.rb +39 -25
- data/lib/eco/api/usecases/ooze_samples/helpers/exportable_register.rb +13 -15
- data/lib/eco/api/usecases/ooze_samples/helpers/filters.rb +50 -21
- data/lib/eco/api/usecases/ooze_samples/helpers/ooze_handlers.rb +21 -11
- data/lib/eco/api/usecases/ooze_samples/helpers/rescuable.rb +2 -0
- data/lib/eco/api/usecases/ooze_samples/helpers/shortcuts.rb +49 -43
- data/lib/eco/api/usecases/ooze_samples/ooze_base_case.rb +17 -19
- data/lib/eco/api/usecases/ooze_samples/register_export_case.rb +48 -43
- data/lib/eco/api/usecases/ooze_samples/register_update_case.rb +33 -34
- data/lib/eco/api/usecases/ooze_samples/target_oozes_update_case.rb +8 -10
- data/lib/eco/api/usecases.rb +0 -1
- data/lib/eco/cli/config/use_cases.rb +31 -29
- data/lib/eco/cli_default/workflow.rb +13 -14
- data/lib/eco/csv/table.rb +34 -25
- data/lib/eco/data/hashes/array_diff.rb +24 -35
- data/lib/eco/data/hashes/diff_result/meta.rb +131 -0
- data/lib/eco/data/hashes/diff_result.rb +65 -57
- data/lib/eco/data/hashes/sanke_camel_indifferent_access.rb +278 -0
- data/lib/eco/data/hashes.rb +1 -1
- data/lib/eco/data/locations/convert.rb +1 -1
- data/lib/eco/data/locations/node_base/csv_convert.rb +19 -9
- data/lib/eco/data/locations/node_base/parsing.rb +4 -2
- data/lib/eco/data/locations/node_base/treeify.rb +149 -132
- data/lib/eco/data/locations/node_base.rb +15 -4
- data/lib/eco/data/locations/node_diff/accessors.rb +13 -5
- data/lib/eco/data/locations/node_diff/nodes_diff/clustered_treeify.rb +101 -0
- data/lib/eco/data/locations/node_diff/nodes_diff/diffs_tree.rb +99 -0
- data/lib/eco/data/locations/node_diff/{selectors.rb → nodes_diff/selectors.rb} +1 -1
- data/lib/eco/data/locations/node_diff/nodes_diff.rb +50 -35
- data/lib/eco/data/locations/node_diff.rb +45 -17
- data/lib/eco/data/locations/node_level/parsing.rb +15 -21
- data/lib/eco/data/locations/node_level.rb +66 -22
- data/lib/eco/data/locations/node_plain/parsing.rb +1 -1
- data/lib/eco/data/locations/node_plain.rb +60 -7
- data/lib/eco/data/strings/camel_case.rb +35 -0
- data/lib/eco/data/strings/snake_case.rb +18 -0
- data/lib/eco/data/strings.rb +8 -0
- data/lib/eco/data.rb +1 -0
- data/lib/eco/language/methods/call_detector.rb +11 -0
- data/lib/eco/language/methods/dsl_able.rb +7 -1
- data/lib/eco/language/methods.rb +2 -1
- data/lib/eco/language/models/collection.rb +23 -25
- data/lib/eco/language/models/parser_serializer.rb +24 -5
- data/lib/eco/version.rb +1 -1
- data/lib/eco-helpers.rb +0 -1
- metadata +52 -7
- data/lib/eco/data/hashes/diff_meta.rb +0 -52
@@ -0,0 +1,99 @@
|
|
1
|
+
class Eco::Data::Locations::NodeDiff::NodesDiff
|
2
|
+
# Helper class to wrap into a tree a set of diffs
|
3
|
+
class DiffsTree
|
4
|
+
class CyclicHierarchy < StandardError
|
5
|
+
end
|
6
|
+
class CyclicAncestorsChain < CyclicHierarchy
|
7
|
+
end
|
8
|
+
|
9
|
+
MAX_ITERATIONS = 16
|
10
|
+
|
11
|
+
attr_reader :id
|
12
|
+
attr_reader :diff
|
13
|
+
attr_reader :children, :parent
|
14
|
+
|
15
|
+
def initialize(diff = nil, id:)
|
16
|
+
@diff = diff
|
17
|
+
@id = id
|
18
|
+
@children = []
|
19
|
+
end
|
20
|
+
|
21
|
+
# @note added explicit setter so we can warn
|
22
|
+
def diff=(value)
|
23
|
+
puts "WARNING (DiffsTree#diff=) was already present (for '#{id}')" if diff
|
24
|
+
@diff = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def diff?
|
28
|
+
!!diff
|
29
|
+
end
|
30
|
+
|
31
|
+
def parent_present?
|
32
|
+
!!parent && parent.diff?
|
33
|
+
end
|
34
|
+
|
35
|
+
# @note that the abscense of `parent_id` (i.e. `nil`) does NOT
|
36
|
+
# mean that this node does not have an actual parent. This class
|
37
|
+
# supports building partial trees (clusters), where some parents
|
38
|
+
# may not have presence.
|
39
|
+
def parent_id
|
40
|
+
return nil unless parent
|
41
|
+
parent.id
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_child(child_tree)
|
45
|
+
raise ArgumentError, "Expecting #{self.class}. Given: #{child_tree.class}" unless child_tree.is_a?(self.class)
|
46
|
+
prevent_cyclic_chain(child_tree)
|
47
|
+
children.push(child_tree)
|
48
|
+
child_tree.link_parent(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def ancestors(iteration: 0)
|
52
|
+
msg = "Node '#{id}' can't be the #{iteration}th ancestor of any other node."
|
53
|
+
raise CyclicAncestorsChain, msg if max_iterations?(iteration)
|
54
|
+
|
55
|
+
return [] unless parent
|
56
|
+
[parent].concat(parent.ancestors(iteration: iteration + 1))
|
57
|
+
end
|
58
|
+
|
59
|
+
def all_descendants(iteration: 0)
|
60
|
+
msg = "Node '#{id}' can't be the #{iteration}th offspring of any other node."
|
61
|
+
raise CyclicAncestorsChain, msg if max_iterations?(iteration)
|
62
|
+
|
63
|
+
children.each_with_object([]) do |child, out|
|
64
|
+
out.push(child)
|
65
|
+
out.concat(child.all_descendants(iteration: iteration + 1))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def link_parent(parent_node)
|
72
|
+
raise ArgumentError, "Expecting #{self.class}. Given: #{parent_node.class}" unless parent_node.is_a?(self.class)
|
73
|
+
return (@parent = parent_node) unless parent
|
74
|
+
msg = "Node '#{id}' already has a parent (#{parent.id}). "
|
75
|
+
msg << "Can't make it child of '#{parent_node.id}' as well."
|
76
|
+
raise CyclicHierarchy, msg
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def prevent_cyclic_chain(child)
|
82
|
+
msg = "Can't make '#{id}' child of itself."
|
83
|
+
raise CyclicHierarchy, msg if child == self
|
84
|
+
|
85
|
+
msg << " A duplicated node got created somehow (review code)"
|
86
|
+
raise CyclicHierarchy, msg if child.id == id
|
87
|
+
|
88
|
+
return true unless ancestors.include?(child)
|
89
|
+
|
90
|
+
msg = "Can't add '#{child.id}' as children of '#{id}' "
|
91
|
+
msg << "because the child is ancestor"
|
92
|
+
raise CyclicHierarchy, msg
|
93
|
+
end
|
94
|
+
|
95
|
+
def max_iterations?(value)
|
96
|
+
value >= self.class::MAX_ITERATIONS
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -1,10 +1,19 @@
|
|
1
1
|
class Eco::Data::Locations::NodeDiff
|
2
2
|
# Adjusts ArrayDiff for Nodes diffs in a location structure
|
3
3
|
class NodesDiff < Eco::Data::Hashes::ArrayDiff
|
4
|
-
|
4
|
+
require_relative 'nodes_diff/selectors'
|
5
|
+
require_relative 'nodes_diff/diffs_tree'
|
6
|
+
require_relative 'nodes_diff/clustered_treeify'
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
+
extend Eco::Data::Locations::NodeDiff::NodesDiff::Selectors
|
9
|
+
|
10
|
+
SELECTORS = %i[
|
11
|
+
id name id_name classifications
|
12
|
+
insert unarchive
|
13
|
+
update move
|
14
|
+
archive
|
15
|
+
].freeze
|
16
|
+
selector(*SELECTORS)
|
8
17
|
|
9
18
|
class_resolver :diff_result_class, Eco::Data::Locations::NodeDiff
|
10
19
|
attr_reader :original_tree
|
@@ -15,78 +24,84 @@ class Eco::Data::Locations::NodeDiff
|
|
15
24
|
end
|
16
25
|
|
17
26
|
def diffs
|
18
|
-
@diffs ||= super.select do |
|
19
|
-
|
27
|
+
@diffs ||= super.select do |dff|
|
28
|
+
# discard entries that are to be inserted and archived at the same time
|
29
|
+
next false if dff.insert? && dff.archive?(validate: false)
|
30
|
+
dff.unarchive? || dff.id_name? || dff.insert? ||
|
31
|
+
dff.move? || dff.archive?
|
20
32
|
end
|
21
33
|
end
|
22
34
|
|
23
|
-
def diffs_details
|
35
|
+
def diffs_details # rubocop:disable Metrics/AbcSize
|
24
36
|
section = '#' * 10
|
25
|
-
msg
|
37
|
+
msg = ''
|
38
|
+
|
26
39
|
if insert?
|
27
40
|
msg << " #{section} I N S E R T S #{section}\n"
|
28
|
-
msg << insert.map {|
|
41
|
+
msg << insert.map {|dff| dff.diff_hash.pretty_inspect}.join
|
29
42
|
end
|
30
43
|
|
31
44
|
if update?
|
32
45
|
msg << "\n #{section} U P D A T E S #{section}\n"
|
33
|
-
update.each do |
|
46
|
+
update.each do |dff|
|
34
47
|
flags = ''
|
35
|
-
|
36
|
-
flags << 'n' if
|
37
|
-
flags << '
|
38
|
-
flags << '
|
48
|
+
flags << 'i' if dff.id?
|
49
|
+
flags << 'n' if dff.name?
|
50
|
+
flags << 'c' if dff.classifications?
|
51
|
+
flags << 'm' if dff.move?
|
52
|
+
flags << 'u' if dff.unarchive?
|
39
53
|
msg << "<< #{flags} >> "
|
40
|
-
msg <<
|
54
|
+
msg << dff.diff_hash.pretty_inspect
|
41
55
|
end
|
42
56
|
end
|
43
57
|
|
44
58
|
if archive?
|
45
59
|
msg << "\n #{section} A R C H I V E S #{section}\n"
|
46
|
-
msg << archive.map {|
|
60
|
+
msg << archive.map {|dff| dff.diff_hash.pretty_inspect}.join
|
47
61
|
end
|
48
62
|
|
49
63
|
msg
|
50
64
|
end
|
51
65
|
|
52
|
-
def diffs_summary
|
53
|
-
comp = "(#{
|
66
|
+
def diffs_summary # rubocop:disable Metrics/AbcSize
|
67
|
+
comp = "(#{source_2.count} input nodes VS #{source_1.count} live nodes)"
|
54
68
|
return "There were no differences identified #{comp}" if diffs.empty?
|
55
69
|
msg = []
|
56
|
-
msg << "Identified #{diffs.count} differences
|
57
|
-
msg << when_present(insert
|
70
|
+
msg << "Identified #{diffs.count} differences #{comp}:"
|
71
|
+
msg << when_present(insert) do |count|
|
58
72
|
" • #{count} nodes to insert"
|
59
73
|
end
|
60
|
-
msg << when_present(update
|
74
|
+
msg << when_present(update) do |count|
|
61
75
|
" • #{count} nodes to update"
|
62
76
|
end
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
msg << when_present(
|
77
|
+
msg << when_present(unarchive) do |count|
|
78
|
+
" • #{count} nodes to unarchive (includes ancestors of target nodes)"
|
79
|
+
end
|
80
|
+
msg << when_present(id) do |count|
|
81
|
+
" • #{count} nodes to change id\n"
|
82
|
+
end
|
83
|
+
msg << when_present(name) do |count|
|
67
84
|
" • #{count} nodes to change name"
|
68
85
|
end
|
69
|
-
msg << when_present(
|
70
|
-
" • #{count} nodes to
|
86
|
+
msg << when_present(classifications) do |count|
|
87
|
+
" • #{count} nodes to change classifications"
|
71
88
|
end
|
72
|
-
msg << when_present(
|
73
|
-
" • #{count} nodes to
|
89
|
+
msg << when_present(move) do |count|
|
90
|
+
" • #{count} nodes to move"
|
74
91
|
end
|
75
|
-
msg << when_present(archive
|
92
|
+
msg << when_present(archive) do |count|
|
76
93
|
" • #{count} nodes to archive"
|
77
94
|
end
|
78
|
-
msg.join("\n")
|
95
|
+
msg.compact.join("\n")
|
79
96
|
end
|
80
97
|
|
81
98
|
private
|
82
99
|
|
83
100
|
def when_present(list, default = nil)
|
101
|
+
raise ArgumentError, "Expecting block but not given" unless block_given?
|
84
102
|
count = list.count
|
85
|
-
|
86
|
-
|
87
|
-
else
|
88
|
-
default
|
89
|
-
end
|
103
|
+
return yield(count) if count&.positive?
|
104
|
+
default
|
90
105
|
end
|
91
106
|
end
|
92
107
|
end
|
@@ -7,49 +7,77 @@ module Eco::Data::Locations
|
|
7
7
|
# - `archived`
|
8
8
|
# @note other properties can be part of the `Hash` although
|
9
9
|
# they may not influence the results.
|
10
|
+
# @note that for special `exposed` methods question mark `?` on a
|
11
|
+
# property of the model checks if that property changed
|
12
|
+
# (it has nothing to do with checking the boolean value of that property)
|
10
13
|
class NodeDiff < Eco::Data::Hashes::DiffResult
|
11
14
|
require_relative 'node_diff/accessors'
|
12
|
-
require_relative 'node_diff/selectors'
|
13
15
|
require_relative 'node_diff/nodes_diff'
|
14
16
|
|
15
17
|
include Eco::Data::Locations::NodeDiff::Accessors
|
16
18
|
|
17
19
|
key :nodeId
|
18
|
-
|
19
|
-
|
20
|
+
# it also adds the snake_methods
|
21
|
+
# i.e. `parent_id`, `parent_id_prev`, `parent_id?` (to check if updated)
|
22
|
+
attr_expose :nodeId, :name, :parentId, :archived, :archivedToken, :classifications
|
20
23
|
|
21
|
-
|
24
|
+
compare :parentId, :name, :archived
|
25
|
+
compare :classifications #, when_present: true
|
22
26
|
|
23
|
-
|
27
|
+
case_sensitive false
|
24
28
|
|
25
|
-
|
29
|
+
# move method, to redefine it
|
30
|
+
alias_method :name_src?, :name?
|
26
31
|
# Has the property `name` changed?
|
27
|
-
|
28
|
-
|
32
|
+
# @note should not be part of an insert
|
33
|
+
def name?
|
34
|
+
name_src? && update?
|
29
35
|
end
|
30
|
-
|
31
|
-
alias_method :
|
32
|
-
#
|
33
|
-
|
36
|
+
|
37
|
+
alias_method :classifications_src?, :classifications?
|
38
|
+
# @note should not be part of an insert
|
39
|
+
def classifications?
|
40
|
+
classifications_src? && update?
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method :insert?, :new?
|
44
|
+
# node id diff? check should be performed as a `key`
|
45
|
+
alias_method :id?, :key?
|
46
|
+
alias_method :nodeId?, :id?
|
47
|
+
alias_method :node_id?, :nodeId?
|
34
48
|
|
35
49
|
# Has any of `id` or `name` properties changed?
|
36
50
|
def id_name?
|
37
|
-
|
51
|
+
return true if id?
|
52
|
+
return true if name?
|
53
|
+
classifications?
|
38
54
|
end
|
39
55
|
|
40
56
|
# Has the parent id changed?
|
41
57
|
def move?
|
42
|
-
|
58
|
+
return false unless update?
|
59
|
+
parent_id?
|
43
60
|
end
|
44
61
|
|
45
62
|
# Has the `archived` property changed and it was `true`?
|
46
63
|
def unarchive?
|
47
|
-
|
64
|
+
return false if archived
|
65
|
+
return false unless update?
|
66
|
+
archived?
|
48
67
|
end
|
49
68
|
|
50
69
|
# Has the `archived` property changed and it was `false`?
|
51
|
-
def archive?
|
52
|
-
|
70
|
+
def archive?(validate: true)
|
71
|
+
return false if archived_prev
|
72
|
+
|
73
|
+
msg = "Value of archived shouldn't be true, "
|
74
|
+
msg << "because node '#{node_id}' doesn't exist "
|
75
|
+
msg << "(it's being inserted). "
|
76
|
+
msg << "It should have been discarded as a diff"
|
77
|
+
raise msg if validate && archived && insert?
|
78
|
+
|
79
|
+
return true if del?
|
80
|
+
archived
|
53
81
|
end
|
54
82
|
end
|
55
83
|
end
|
@@ -28,34 +28,28 @@ class Eco::Data::Locations::NodeLevel
|
|
28
28
|
def nodes_from_csv(csv)
|
29
29
|
raise ArgumentError, "Expecting CSV::Table. Given: #{csv.class}" unless csv.is_a?(::CSV::Table)
|
30
30
|
|
31
|
-
|
32
|
-
prev_node = nil
|
31
|
+
possible_classifications = csv.headers
|
33
32
|
|
34
33
|
# Convert to Eco::CSV::Table for a fresh start
|
35
34
|
csv = Eco::CSV.parse(csv.to_csv).nil_blank_cells.add_index_column(:row_num)
|
36
35
|
|
37
|
-
|
38
|
-
nodes
|
36
|
+
prev_node = nil
|
37
|
+
nodes = csv.each_with_object([]) do |row, out|
|
39
38
|
row_num, *values = row.fields
|
40
|
-
node = node_class.new(row_num, *values)
|
41
|
-
prev_node ||= node
|
42
39
|
|
43
|
-
|
44
|
-
|
45
|
-
# Make sure upper level tags are there (including parent)
|
46
|
-
# This normalizes input to all upper level tags always filled in
|
47
|
-
# which allows to node#actual_level to work
|
48
|
-
node.set_high_levels(prev_node)
|
49
|
-
else
|
50
|
-
if node.raw_level == 1
|
51
|
-
# It is expected not to have parent (as it's top level tag)
|
52
|
-
elsif prev_node
|
53
|
-
node.set_high_levels(prev_node)
|
54
|
-
else
|
55
|
-
raise "Node '#{node.raw_tag}' (#{node.row_num} row) doesn't have parent"
|
56
|
-
end
|
40
|
+
node = node_class.new(row_num, *values).tap do |nd|
|
41
|
+
nd.original_headers = possible_classifications
|
57
42
|
end
|
58
|
-
|
43
|
+
|
44
|
+
out << node
|
45
|
+
prev_node ||= node
|
46
|
+
|
47
|
+
# unless top level
|
48
|
+
# Make sure upper level tags are there (including parent)
|
49
|
+
# This normalizes input to all upper level tags always filled in
|
50
|
+
# which allows to node#actual_level to work
|
51
|
+
node.set_high_levels(prev_node) unless node.raw_level == 1
|
52
|
+
|
59
53
|
prev_node = node
|
60
54
|
end
|
61
55
|
tidy_nodes(nodes)
|
@@ -1,5 +1,6 @@
|
|
1
|
+
# rubocop:disable Naming/AccessorMethodName
|
1
2
|
module Eco::Data::Locations
|
2
|
-
NODE_LEVEL_ATTRS = %i[row_num l1 l2 l3 l4 l5 l6 l7 l8 l9 l10 l11]
|
3
|
+
NODE_LEVEL_ATTRS = %i[row_num l1 l2 l3 l4 l5 l6 l7 l8 l9 l10 l11].freeze
|
3
4
|
NodeLevelStruct = Struct.new(*NODE_LEVEL_ATTRS)
|
4
5
|
# Class to treat input csv in a form of levels, where each column is one level,
|
5
6
|
# and children are placed in higher columns right below the parent.
|
@@ -13,28 +14,69 @@ module Eco::Data::Locations
|
|
13
14
|
extend Eco::Data::Locations::NodeLevel::Builder
|
14
15
|
|
15
16
|
ALL_ATTRS = NODE_LEVEL_ATTRS
|
16
|
-
ADDITIONAL_ATTRS = %i[row_num]
|
17
|
-
TAGS_ATTRS = ALL_ATTRS - ADDITIONAL_ATTRS
|
17
|
+
ADDITIONAL_ATTRS = %i[row_num].freeze
|
18
|
+
TAGS_ATTRS = (ALL_ATTRS - ADDITIONAL_ATTRS).freeze
|
19
|
+
|
20
|
+
# @yield [node, json] optional custom serializer
|
21
|
+
# @yieldparam node [Node] self
|
22
|
+
# @yieldparam json [Hash] the default serialization
|
23
|
+
# @yieldreturn [Hash] the serialized Node
|
24
|
+
def node_hash(stringify_keys: true)
|
25
|
+
json = to_h(:id, :name, :parent_id, :classifications, :classification_names)
|
26
|
+
json.transform_keys!(&:to_s) if stringify_keys
|
27
|
+
json.merge!(yield(self, json)) if block_given?
|
28
|
+
json
|
29
|
+
end
|
30
|
+
|
31
|
+
def copy
|
32
|
+
super.tap do |cp|
|
33
|
+
cp.highest_levels_set!
|
34
|
+
cp.original_headers = original_headers
|
35
|
+
end
|
36
|
+
end
|
18
37
|
|
19
|
-
|
38
|
+
# In node level the parent id is set via parsing
|
39
|
+
attr_accessor :parentId # rubocop:disable Naming/MethodName
|
40
|
+
alias_method :parent_id=, :parentId=
|
41
|
+
alias_method :parent_id, :parentId
|
20
42
|
|
21
43
|
def id
|
22
44
|
tag.upcase
|
23
45
|
end
|
24
|
-
alias_method :nodeId,
|
46
|
+
alias_method :nodeId, :id
|
47
|
+
alias_method :node_id, :nodeId
|
25
48
|
|
26
49
|
def name
|
27
50
|
tag
|
28
51
|
end
|
29
52
|
|
30
53
|
def tag
|
31
|
-
clean_id(raw_tag, ref: "(Row: #{
|
54
|
+
clean_id(raw_tag, ref: "(Row: #{row_num}) ")
|
32
55
|
end
|
33
56
|
|
34
57
|
def raw_tag
|
35
58
|
values_at(*TAGS_ATTRS.reverse).compact.first
|
36
59
|
end
|
37
60
|
|
61
|
+
attr_writer :original_headers
|
62
|
+
|
63
|
+
def original_headers
|
64
|
+
@original_headers ||= []
|
65
|
+
end
|
66
|
+
|
67
|
+
def original_headers?
|
68
|
+
original_headers.any?
|
69
|
+
end
|
70
|
+
|
71
|
+
def classifications
|
72
|
+
return [] unless (al = actual_level).positive?
|
73
|
+
original_headers[al - 1]
|
74
|
+
end
|
75
|
+
|
76
|
+
def classification_names
|
77
|
+
classifications.dup
|
78
|
+
end
|
79
|
+
|
38
80
|
def level
|
39
81
|
actual_level
|
40
82
|
end
|
@@ -56,7 +98,7 @@ module Eco::Data::Locations
|
|
56
98
|
|
57
99
|
def raw_prev_empty_level?
|
58
100
|
lev = raw_prev_empty_level
|
59
|
-
lev
|
101
|
+
lev&.positive?
|
60
102
|
end
|
61
103
|
|
62
104
|
def raw_latest_consecutive_top_empty_level
|
@@ -72,10 +114,11 @@ module Eco::Data::Locations
|
|
72
114
|
otags_array = other.tags_array.compact
|
73
115
|
stags_array = tags_array.compact
|
74
116
|
raise "Missing lower levels for #{other.id}: #{other.tags_array.pretty_inspect}" unless other.highest_levels_set?
|
75
|
-
raise "Missing lower levels for #{
|
117
|
+
raise "Missing lower levels for #{id}: #{tags_array.pretty_inspect}" unless highest_levels_set?
|
118
|
+
|
76
119
|
otags_array.zip(stags_array).each_with_index do |(otag, stag), idx|
|
77
|
-
next
|
78
|
-
return nil if idx
|
120
|
+
next if otag&.upcase&.strip == stag&.upcase&.strip
|
121
|
+
return nil if idx&.zero?
|
79
122
|
return idx # previous idx, which means prev_idx + 1 (so idx itself)
|
80
123
|
end
|
81
124
|
actual_level
|
@@ -96,18 +139,12 @@ module Eco::Data::Locations
|
|
96
139
|
|
97
140
|
def previous_idx
|
98
141
|
idx = tag_idx - 1
|
99
|
-
idx
|
142
|
+
idx&.negative?? nil : idx
|
100
143
|
end
|
101
144
|
|
102
145
|
def empty_idx
|
103
146
|
tary = tags_array
|
104
|
-
tary.index(nil) || tary.length + 1
|
105
|
-
end
|
106
|
-
|
107
|
-
def copy
|
108
|
-
super.tap do |dup|
|
109
|
-
dup.highest_levels_set!
|
110
|
-
end
|
147
|
+
tary.index(nil) || (tary.length + 1)
|
111
148
|
end
|
112
149
|
|
113
150
|
# We got a missing level that is compacted in one row
|
@@ -120,8 +157,11 @@ module Eco::Data::Locations
|
|
120
157
|
# must be the last among filled_idxs, so let's use it to verify
|
121
158
|
unless with_info.last == tag_idx
|
122
159
|
# This can only happen when there are repeated nodes
|
123
|
-
|
160
|
+
msg = "Review this (row #{row_num}; '#{raw_tag}'): "
|
161
|
+
msg << "tag_idx is #{tag_idx}, while last filled idx is #{with_info.last}"
|
162
|
+
raise msg
|
124
163
|
end
|
164
|
+
|
125
165
|
len = with_info.length
|
126
166
|
target_idxs = with_info[len-(num+1)..-2]
|
127
167
|
target_idxs.map do |idx|
|
@@ -142,8 +182,8 @@ module Eco::Data::Locations
|
|
142
182
|
end
|
143
183
|
|
144
184
|
# Sets ancestors
|
145
|
-
def set_high_levels(node
|
146
|
-
update_lower_levels(node.tags_array
|
185
|
+
def set_high_levels(node)
|
186
|
+
update_lower_levels(node.tags_array)
|
147
187
|
self
|
148
188
|
end
|
149
189
|
|
@@ -159,6 +199,7 @@ module Eco::Data::Locations
|
|
159
199
|
return false
|
160
200
|
end
|
161
201
|
return false if target.empty?
|
202
|
+
|
162
203
|
target.each do |n|
|
163
204
|
#puts "clearing 'l#{n}': #{attr("l#{n}")}"
|
164
205
|
set_attr("l#{n}", nil)
|
@@ -169,9 +210,10 @@ module Eco::Data::Locations
|
|
169
210
|
# Ensures parent is among the upper level tags
|
170
211
|
# It actually ensures all ancestors are there
|
171
212
|
# @param override [Boolean] `false` will only override upmost top consecutive empty levels.
|
172
|
-
def update_lower_levels(src_tags_array, to_level:
|
213
|
+
def update_lower_levels(src_tags_array, to_level: raw_latest_consecutive_top_empty_level)
|
173
214
|
highest_levels_set!
|
174
215
|
return self unless to_level
|
216
|
+
|
175
217
|
target_lev = Array(1..to_level)
|
176
218
|
target_tags = src_tags_array[level_to_idx(1)..level_to_idx(to_level)]
|
177
219
|
target_lev.zip(target_tags).each do |(n, tag)|
|
@@ -214,3 +256,5 @@ module Eco::Data::Locations
|
|
214
256
|
end
|
215
257
|
end
|
216
258
|
end
|
259
|
+
|
260
|
+
# rubocop:enable Naming/AccessorMethodName
|
@@ -8,7 +8,7 @@ class Eco::Data::Locations::NodePlain
|
|
8
8
|
|
9
9
|
# Subset of property names expected to be among the headers
|
10
10
|
def basic_headers
|
11
|
-
@
|
11
|
+
@basic_headers ||= %w[id name parent_id]
|
12
12
|
end
|
13
13
|
|
14
14
|
# Checks if the `basic_headers` to process as `NodePlain` are present.
|
@@ -1,6 +1,13 @@
|
|
1
1
|
module Eco::Data::Locations
|
2
|
-
NODE_PLAIN_ATTRS = %i[
|
3
|
-
|
2
|
+
NODE_PLAIN_ATTRS = %i[
|
3
|
+
row_num
|
4
|
+
id name parent_id
|
5
|
+
weight
|
6
|
+
archived archive_token
|
7
|
+
classifications
|
8
|
+
classification_names
|
9
|
+
].freeze
|
10
|
+
NodePlainStruct = Struct.new(*NODE_PLAIN_ATTRS)
|
4
11
|
# Class to treat input csv in a form of a list of nodes, where parent is specified.
|
5
12
|
class NodePlain < NodePlainStruct
|
6
13
|
include Eco::Data::Locations::NodeBase
|
@@ -11,22 +18,68 @@ module Eco::Data::Locations
|
|
11
18
|
extend Eco::Data::Locations::NodePlain::Builder
|
12
19
|
|
13
20
|
ALL_ATTRS = NODE_PLAIN_ATTRS
|
14
|
-
ADDITIONAL_ATTRS = %i[row_num]
|
15
|
-
PROP_ATTRS = ALL_ATTRS - ADDITIONAL_ATTRS
|
21
|
+
ADDITIONAL_ATTRS = %i[row_num].freeze
|
22
|
+
PROP_ATTRS = (ALL_ATTRS - ADDITIONAL_ATTRS).freeze
|
16
23
|
|
17
24
|
def id
|
18
|
-
clean_id(super, ref: "(Row: #{
|
25
|
+
clean_id(super, ref: "(Row: #{row_num}) ")
|
19
26
|
end
|
20
27
|
# backwards compatibility
|
21
28
|
alias_method :tag, :id
|
22
29
|
|
23
30
|
def name
|
24
|
-
super ||
|
31
|
+
super || id
|
25
32
|
end
|
26
33
|
|
27
34
|
def parent_id
|
28
|
-
clean_id(super, notify: false, ref: "(Row: #{
|
35
|
+
clean_id(super, notify: false, ref: "(Row: #{row_num} - parent_id) ")
|
29
36
|
end
|
30
37
|
alias_method :parentId, :parent_id
|
38
|
+
|
39
|
+
def archived
|
40
|
+
value = super
|
41
|
+
return false if value.nil? || value == false
|
42
|
+
return true if value == true
|
43
|
+
return false if value.to_s.strip.empty?
|
44
|
+
return true if %w[yes x true].include?(value.downcase)
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
def classifications
|
49
|
+
into_a(super).map do |value|
|
50
|
+
treat_classication(value)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def classification_names
|
55
|
+
into_a(super)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @yield [node, json] optional custom serializer
|
59
|
+
# @yieldparam node [Node] self
|
60
|
+
# @yieldparam json [Hash] the default serialization
|
61
|
+
# @yieldreturn [Hash] the serialized Node
|
62
|
+
def node_hash(stringify_keys: true)
|
63
|
+
json = to_h.reject {|key, _v| key == :row_num}
|
64
|
+
json.transform_keys!(&:to_s) if stringify_keys
|
65
|
+
json.merge!(yield(self, json)) if block_given?
|
66
|
+
json
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def treat_classication(value)
|
72
|
+
return value unless value.is_a?(String)
|
73
|
+
value.strip.gsub(/\W+/, '').downcase
|
74
|
+
end
|
75
|
+
|
76
|
+
# Helper to convert to array
|
77
|
+
def into_a(value)
|
78
|
+
if value.is_a?(String)
|
79
|
+
value.split('|')
|
80
|
+
else
|
81
|
+
[value].flatten
|
82
|
+
end.compact
|
83
|
+
end
|
31
84
|
end
|
32
85
|
end
|