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
@@ -12,7 +12,9 @@ module Eco::Data::Locations::NodeBase
|
|
12
12
|
raise ArgumentError, "The input csv does not have the required format to read a locations structure."
|
13
13
|
end
|
14
14
|
|
15
|
-
# @yield [
|
15
|
+
# @yield [node, json] optional custom serializer
|
16
|
+
# @yieldparam node [Node] the node that is being serialized
|
17
|
+
# @yieldparam json [Hash] the default serialization
|
16
18
|
# @yieldreturn [Hash] the serialized Node
|
17
19
|
# @return [Array<Hash>] a hierarchical tree of nested Hashes via `nodes` key.
|
18
20
|
def hash_tree_from_csv(csv, &block)
|
@@ -24,7 +26,7 @@ module Eco::Data::Locations::NodeBase
|
|
24
26
|
# @param filename [String] the csv file.
|
25
27
|
# @return [Array<NodePlain>, Array<NodeLevel>] with integrity issues resolved.
|
26
28
|
def csv_nodes_from(filename, encoding: 'utf-8')
|
27
|
-
nodes_from_csv(csv_from(filename, encoding:
|
29
|
+
nodes_from_csv(csv_from(filename, encoding: encoding))
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
@@ -7,6 +7,20 @@ module Eco::Data::Locations::NodeBase
|
|
7
7
|
module Treeify
|
8
8
|
include Eco::Language::AuxiliarLogger
|
9
9
|
|
10
|
+
# @yield [node, json] optional custom serializer
|
11
|
+
# @yieldparam node [Node] the node that is being serialized
|
12
|
+
# @yieldparam json [Hash] the default serialization
|
13
|
+
# @yieldreturn [Hash] the serialized Node
|
14
|
+
def serialize_node(node, parent_id: :unused)
|
15
|
+
msg = "Expecting Eco::Data::Locations::NodeBase. Given: #{node.class}"
|
16
|
+
raise ArgumentError, msg unless node.is_a?(Eco::Data::Locations::NodeBase)
|
17
|
+
|
18
|
+
node.node_hash.tap do |json|
|
19
|
+
json.merge!({"parent_id" => parent_id}) unless parent_id == :unused
|
20
|
+
json.merge!(yield(node, json)) if block_given?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
10
24
|
# @note if block is no given, it auto-detects the `serializer` **block**.
|
11
25
|
# @yield [NodeBase] for each included node
|
12
26
|
# @yieldreturn [Hash] custom hash model when treeifying (allows to set more keys/properties).
|
@@ -14,20 +28,20 @@ module Eco::Data::Locations::NodeBase
|
|
14
28
|
# @return [Array<Hash>] a hierarchical tree of nested Hashes via `nodes` key.
|
15
29
|
def treeify(nodes, skipped: [], unlinked_trees: [], &block)
|
16
30
|
return [] if nodes.empty?
|
17
|
-
block
|
31
|
+
block ||= nodes.first.class.serializer
|
18
32
|
done_ids = {}
|
19
33
|
warns = []
|
20
34
|
parents = parents_hash(nodes)
|
21
|
-
get_children(
|
35
|
+
get_children(
|
36
|
+
nil, parents,
|
37
|
+
done_ids: done_ids, skipped: skipped,
|
38
|
+
warns: warns, &block
|
39
|
+
).tap do |tree|
|
22
40
|
check_results(
|
23
|
-
tree,
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
skipped: skipped,
|
28
|
-
unlinked_trees: unlinked_trees,
|
29
|
-
warns: warns,
|
30
|
-
&block
|
41
|
+
tree, nodes,
|
42
|
+
parents, done_ids: done_ids,
|
43
|
+
skipped: skipped, unlinked_trees: unlinked_trees,
|
44
|
+
warns: warns, &block
|
31
45
|
)
|
32
46
|
log(:warn) { warns.join("\n") } unless warns.empty?
|
33
47
|
end
|
@@ -49,56 +63,44 @@ module Eco::Data::Locations::NodeBase
|
|
49
63
|
# 3. The above can translate into some
|
50
64
|
# @yield [node]
|
51
65
|
# @yieldreturn [Hash] custom hash model when treeifying
|
52
|
-
def get_children(
|
66
|
+
def get_children(
|
67
|
+
node_id, parents,
|
68
|
+
parent: nil, level: 0,
|
69
|
+
done_ids: {}, skipped: [],
|
70
|
+
warns: [], &block
|
71
|
+
)
|
53
72
|
level_ids = []
|
54
73
|
(parents[node_id] ||= []).each_with_object([]) do |child, results|
|
55
74
|
# Skipping done id. Add proper warnings...
|
56
75
|
# => rely on `done_ids` to identify if an `id` has already been done
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
warns: warns
|
66
|
-
) if done_ids[child.id]
|
76
|
+
if done_ids[child.id]
|
77
|
+
next report_skipped_node(
|
78
|
+
child, parent,
|
79
|
+
done_ids, level,
|
80
|
+
level_ids, parents,
|
81
|
+
skipped: skipped, warns: warns
|
82
|
+
)
|
83
|
+
end
|
67
84
|
|
68
85
|
# Fill in tracking data
|
69
86
|
child.parent = parent
|
70
87
|
child.tracked_level = level + 1
|
71
|
-
level_ids
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
children = get_children(
|
83
|
-
child.id,
|
84
|
-
parents,
|
85
|
-
parent: child,
|
86
|
-
done_ids: done_ids,
|
87
|
-
level: level + 1,
|
88
|
-
skipped: skipped,
|
89
|
-
warns: warns,
|
90
|
-
&block
|
88
|
+
level_ids << child.id
|
89
|
+
done_ids[child.id] = child
|
90
|
+
|
91
|
+
node_hash = serialize_node(child, parent_id: node_id, &block)
|
92
|
+
results << node_hash
|
93
|
+
node_hash["nodes"] = get_children(
|
94
|
+
child.id, parents,
|
95
|
+
parent: child, done_ids: done_ids,
|
96
|
+
level: level + 1, skipped: skipped,
|
97
|
+
warns: warns, &block
|
91
98
|
).tap do |desc|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
99
|
+
next unless (nil_count = desc.count(nil)).positive?
|
100
|
+
log(:debug) {
|
101
|
+
"get_children gave #{nil_count} nil values for nodes of #{child.id}"
|
102
|
+
}
|
97
103
|
end
|
98
|
-
|
99
|
-
results << node_hash.merge({
|
100
|
-
"nodes" => children.compact
|
101
|
-
})
|
102
104
|
end
|
103
105
|
end
|
104
106
|
|
@@ -111,7 +113,7 @@ module Eco::Data::Locations::NodeBase
|
|
111
113
|
end
|
112
114
|
|
113
115
|
def indent(level)
|
114
|
-
"
|
116
|
+
" " * level
|
115
117
|
end
|
116
118
|
|
117
119
|
# Method to ensure the results are consistent
|
@@ -120,83 +122,103 @@ module Eco::Data::Locations::NodeBase
|
|
120
122
|
# because otherwise would be part of `done_ids` anyway.
|
121
123
|
# @param unlinked_trees [Array<Hash>] by excluding those done and skipped,
|
122
124
|
# it will treeify the unlinked nodes (the exclusion applies to `parants_hash`)
|
123
|
-
def check_results(
|
125
|
+
def check_results( # rubocop:disable Metrics/AbcSize
|
126
|
+
_tree, nodes, parents,
|
127
|
+
done_ids: {}, skipped: [], unlinked_trees: [],
|
128
|
+
warns: [], &block
|
129
|
+
)
|
124
130
|
update_skipped(skipped, parents, done_ids: done_ids) unless skipped.empty?
|
131
|
+
return if done_ids.count == nodes.count
|
125
132
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
unlinked_parent_ids = (parents.keys - done_ids.keys).compact
|
133
|
+
tracked_nodes = done_ids.values
|
134
|
+
untracked_nodes = nodes - tracked_nodes - skipped
|
135
|
+
# skipped keys is inherent, as they were excluded because of id clash with done_ids
|
136
|
+
unlinked_parent_ids = (parents.keys - done_ids.keys).compact
|
131
137
|
|
138
|
+
|
139
|
+
# The reason of missing nodes in the output tree is unknown!
|
140
|
+
if skipped.empty? && unlinked_parent_ids.empty?
|
132
141
|
msg = []
|
142
|
+
msg << "BUG in this library (open issue with maintainers)."
|
143
|
+
msg << "There were no skipped nodes nor missin referred parents, and yet:"
|
144
|
+
msg << " • the tree nodes count: #{done_ids.count} ..."
|
145
|
+
msg << " • doesn't match the original nodes count: #{nodes.count}"
|
146
|
+
raise msg.join("\n")
|
147
|
+
end
|
133
148
|
|
134
|
-
|
135
|
-
|
136
|
-
msg << "BUG in this library (open issue with maintainers)."
|
137
|
-
msg << "There were no skipped nodes nor missin referred parents, and yet:"
|
138
|
-
msg << " • the tree nodes count: #{done_ids.count} ..."
|
139
|
-
msg << " • doesn't match the original nodes count: #{nodes.count}"
|
140
|
-
raise msg.join("\n")
|
141
|
-
end
|
149
|
+
unless unlinked_parent_ids.empty?
|
150
|
+
str_skipped = (skipped.count < 15 ? " => #{skipped.map(&:id).join(', ')}" : '')
|
142
151
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
msg << "After treeifying via the unlinked_parents:"
|
172
|
-
msg << " • total_nodes: #{nodes.count}"
|
173
|
-
msg << " • tracked_nodes: #{tracked_nodes.count}"
|
174
|
-
msg << " • untracked_nodes: #{untracked_nodes.count}"
|
175
|
-
msg << " • unlinked_parents: #{unlinked_parent_ids.count}"
|
176
|
-
msg << " • skipped in this step: #{residual_skipped.count}"
|
177
|
-
end
|
152
|
+
msg = []
|
153
|
+
msg << "There are #{unlinked_parent_ids.count} referred parent_id's NOT linked to the root:"
|
154
|
+
msg << " • total_nodes: #{nodes.count}"
|
155
|
+
msg << " • tracked_nodes: #{tracked_nodes.count}"
|
156
|
+
msg << " • untracked_nodes: #{untracked_nodes.count}"
|
157
|
+
msg << " • unlinked_parents: #{unlinked_parent_ids.count}"
|
158
|
+
msg << " • skipped (repeated) nodes: #{skipped.count}#{str_skipped}" unless skipped.empty?
|
159
|
+
warns << msg.join("\n")
|
160
|
+
|
161
|
+
unlinked_nodes = nodes - skipped
|
162
|
+
unlinked_parents = parents.slice(*unlinked_parent_ids) # doesn'thave skipped ones
|
163
|
+
|
164
|
+
residual_skipped = []
|
165
|
+
ulnk_trees = get_unlinked_trees(
|
166
|
+
unlinked_nodes, unlinked_parents,
|
167
|
+
done_ids: done_ids, skipped: residual_skipped,
|
168
|
+
warns: warns, &block
|
169
|
+
)
|
170
|
+
unlinked_trees.concat(ulnk_trees)
|
171
|
+
|
172
|
+
update_skipped(skipped, parents, with: residual_skipped, done_ids: done_ids) unless residual_skipped.empty?
|
173
|
+
|
174
|
+
tracked_nodes = done_ids.values
|
175
|
+
untracked_nodes = nodes - tracked_nodes - skipped
|
176
|
+
unlinked_parent_ids = (parents.keys - done_ids.keys).compact
|
177
|
+
|
178
|
+
str_unlinked_parents =
|
179
|
+
unlinked_parent_ids.count < 35 ? unlinked_parent_ids.join(', ') : unlinked_parent_ids.count
|
178
180
|
|
179
|
-
|
181
|
+
str_skipped = (residual_skipped.count < 15 ? " => #{residual_skipped.map(&:id).join(', ')}" : '')
|
182
|
+
|
183
|
+
msg = []
|
184
|
+
msg << "After treeifying via the unlinked_parents:"
|
185
|
+
msg << " • total_nodes: #{nodes.count}"
|
186
|
+
msg << " • tracked_nodes: #{tracked_nodes.count}"
|
187
|
+
msg << " • untracked_nodes: #{untracked_nodes.count}"
|
188
|
+
msg << " • missing_parents: #{str_unlinked_parents}"
|
189
|
+
msg << " • skipped in this step: #{residual_skipped.count}#{str_skipped}" unless residual_skipped.empty?
|
180
190
|
warns << msg.join("\n")
|
181
|
-
nil
|
182
191
|
end
|
192
|
+
|
193
|
+
str_skipped = (skipped.count < 15 ? " => #{skipped.map(&:id).join(', ')}" : '')
|
194
|
+
warns << " • total skipped (repeated) nodes: #{skipped.count}!!#{str_skipped}" unless skipped.empty?
|
195
|
+
nil
|
183
196
|
end
|
184
197
|
|
185
198
|
# Treeifies the unlinked nodes by scoping existing parent ids.
|
186
|
-
def get_unlinked_trees(
|
199
|
+
def get_unlinked_trees(
|
200
|
+
nodes, parents,
|
201
|
+
done_ids: {}, skipped: [],
|
202
|
+
warns: [], &block
|
203
|
+
)
|
187
204
|
node_ids = nodes.map(&:id)
|
188
205
|
parent_ids = parents.keys & node_ids
|
189
206
|
missing_parent_ids = parents.keys - parent_ids
|
190
207
|
missing_parents = parents.slice(*missing_parent_ids)
|
191
|
-
warns
|
192
|
-
|
193
|
-
|
194
|
-
|
208
|
+
warns << " • missing_parents: #{missing_parents.count}"
|
209
|
+
|
210
|
+
nil_parent_nodes = missing_parents.each_with_object([]) do |(_id, nds), mem|
|
211
|
+
nds.each {|node| node.parent_id = nil}
|
212
|
+
mem.concat(nds)
|
195
213
|
end
|
196
214
|
rest_parents = parents.slice(*parent_ids).merge({
|
197
215
|
nil => nil_parent_nodes
|
198
216
|
})
|
199
|
-
get_children(
|
217
|
+
get_children(
|
218
|
+
nil, rest_parents,
|
219
|
+
done_ids: done_ids, skipped: skipped,
|
220
|
+
warns: warns, &block
|
221
|
+
)
|
200
222
|
end
|
201
223
|
|
202
224
|
# Same as `get_children` but not performing checks and with
|
@@ -205,28 +227,18 @@ module Eco::Data::Locations::NodeBase
|
|
205
227
|
# skipped, because their parent was skipped.
|
206
228
|
def get_tree_nodes_raw(node_id, parents, src_plain: true, &block)
|
207
229
|
(parents[node_id] ||= []).each_with_object([]) do |child, results|
|
208
|
-
unless src_plain
|
209
|
-
node_hash = {
|
210
|
-
"id" => child.id,
|
211
|
-
"name" => child.name,
|
212
|
-
"parent_id" => node_id
|
213
|
-
}
|
214
|
-
node_hash.merge(yield(child)) if block_given?
|
215
|
-
end
|
216
|
-
|
230
|
+
node_hash = serialize_node(child, parent_id: node_id, &block) unless src_plain
|
217
231
|
descendants = get_tree_nodes_raw(child.id, parents, src_plain: src_plain, &block).tap do |desc|
|
218
|
-
|
219
|
-
|
220
|
-
|
232
|
+
next unless (nil_count = desc.count(nil)).positive?
|
233
|
+
log(:debug) {
|
234
|
+
"get_tree_nodes_raw gave #{nil_count} nil values for nodes of #{child.id}"
|
235
|
+
}
|
221
236
|
end
|
237
|
+
next results.concat(descendants) if src_plain
|
222
238
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
results << node_hash.merge({
|
227
|
-
"nodes" => descendants.compact
|
228
|
-
})
|
229
|
-
end
|
239
|
+
results << node_hash.merge({
|
240
|
+
"nodes" => descendants.compact
|
241
|
+
})
|
230
242
|
end
|
231
243
|
end
|
232
244
|
|
@@ -244,22 +256,27 @@ module Eco::Data::Locations::NodeBase
|
|
244
256
|
|
245
257
|
# With given a skipped `node` (repeated `id`), it gives different warnings,
|
246
258
|
# provided that the context in which the double-up `id` happened is identified.
|
247
|
-
def report_skipped_node(
|
248
|
-
|
259
|
+
def report_skipped_node( # rubocop:disable Metrics/AbcSize
|
260
|
+
node, parent,
|
261
|
+
done_ids, level,
|
262
|
+
level_ids, parents,
|
263
|
+
skipped: [], warns: []
|
264
|
+
)
|
265
|
+
skipped << node
|
249
266
|
lev = level + 1
|
250
267
|
done_node = done_ids[node.id]
|
251
268
|
prev_parent = node.parent
|
252
269
|
prev_level = node.tracked_level
|
253
270
|
node_dup = done_node && (done_node != node)
|
254
271
|
lev_dup = level_ids.include?(node.id)
|
255
|
-
multi_parent = (!prev_parent == !!parent) || (prev_parent && (prev_parent.id != parent.id))
|
272
|
+
multi_parent = (!prev_parent == !!parent) || (prev_parent && (prev_parent.id != parent.id)) # rubocop:disable Style/DoubleNegation
|
256
273
|
|
257
274
|
row_num = node.respond_to?(:row_num) ? node.row_num : nil
|
258
275
|
row_str = row_num ? "(Row: #{row_num}) " : ''
|
259
276
|
node_str = "#{row_str}Node '#{node.id}' #{level_msg(lev)} (#{parent_msg(parent)})"
|
260
277
|
|
261
|
-
msg
|
262
|
-
msg
|
278
|
+
msg = []
|
279
|
+
msg << "#{indent(level)}Skipping #{node_str}."
|
263
280
|
|
264
281
|
# Implementation integrity guard
|
265
282
|
# => as we don't register in `done_ids` those that are skipped,
|
@@ -10,16 +10,16 @@ module Eco::Data::Locations
|
|
10
10
|
require_relative 'node_base/builder'
|
11
11
|
extend Eco::Data::Locations::NodeBase::Builder
|
12
12
|
|
13
|
-
ALL_ATTRS = []
|
13
|
+
ALL_ATTRS = [].freeze
|
14
14
|
|
15
15
|
attr_accessor :tracked_level, :parent
|
16
16
|
|
17
17
|
def copy
|
18
|
-
self.class.new.set_attrs(**
|
18
|
+
self.class.new.set_attrs(**to_h)
|
19
19
|
end
|
20
20
|
|
21
21
|
def attr(sym)
|
22
|
-
|
22
|
+
send(sym.to_sym)
|
23
23
|
end
|
24
24
|
|
25
25
|
def attr?(sym)
|
@@ -32,13 +32,24 @@ module Eco::Data::Locations
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def set_attr(attr, value)
|
35
|
-
|
35
|
+
send("#{attr}=", value)
|
36
36
|
end
|
37
37
|
|
38
38
|
def values_at(*attrs)
|
39
39
|
attrs.map {|a| attr(a)}
|
40
40
|
end
|
41
41
|
|
42
|
+
# @yield [node, json] optional custom serializer
|
43
|
+
# @yieldparam node [Node] self
|
44
|
+
# @yieldparam json [Hash] the default serialization
|
45
|
+
# @yieldreturn [Hash] the serialized Node
|
46
|
+
def node_hash(stringify_keys: true)
|
47
|
+
json = to_h(:id, :name, :parent_id)
|
48
|
+
json.transform_keys!(&:to_s) if stringify_keys
|
49
|
+
json.merge!(yield(self, json)) if block_given?
|
50
|
+
json
|
51
|
+
end
|
52
|
+
|
42
53
|
def to_h(*attrs)
|
43
54
|
attrs = self.class::ALL_ATTRS if attrs.empty?
|
44
55
|
attrs.zip(values_at(*attrs)).to_h
|
@@ -10,30 +10,38 @@ class Eco::Data::Locations::NodeDiff
|
|
10
10
|
end
|
11
11
|
|
12
12
|
module ClassMethods
|
13
|
+
include Eco::Data::Strings::SnakeCase
|
14
|
+
|
13
15
|
# Creates the defined accessor attributes against `NodeDiff`
|
14
16
|
# It also creates a method with question mark that evaluates true if **any** the attr changed.
|
15
17
|
# @note the defined attributes are expected to be the keys within
|
16
18
|
# the source Hashes that are being compared.
|
17
|
-
# @note accessing `
|
19
|
+
# @note accessing `src_1` (prev) attributes, will have it's method as `prev_[attrName]`
|
18
20
|
def attr_expose(*attrs)
|
19
21
|
attrs.each do |attr|
|
20
22
|
meth = attr.to_sym
|
21
|
-
methp = "
|
22
|
-
methq = "
|
23
|
+
methp = "#{meth}_prev".to_sym
|
24
|
+
methq = "#{meth}?".to_sym
|
23
25
|
|
26
|
+
# current value
|
24
27
|
define_method meth do
|
25
28
|
attr(meth)
|
26
29
|
end
|
30
|
+
alias_method snake_case(meth), meth
|
27
31
|
|
32
|
+
# prev value
|
28
33
|
define_method methp do
|
29
34
|
attr_prev(meth)
|
30
35
|
end
|
36
|
+
alias_method snake_case(methp), methp
|
31
37
|
|
32
|
-
|
33
|
-
|
38
|
+
# has it changed?
|
34
39
|
define_method methq do
|
35
40
|
diff_attr?(meth)
|
36
41
|
end
|
42
|
+
alias_method snake_case(methq), methq
|
43
|
+
|
44
|
+
@exposed_attrs = (@exposed_attrs || []) | [meth]
|
37
45
|
end
|
38
46
|
end
|
39
47
|
|
@@ -0,0 +1,101 @@
|
|
1
|
+
class Eco::Data::Locations::NodeDiff::NodesDiff
|
2
|
+
# treeify helper... with diffs you cannot expect all nodes
|
3
|
+
# to be there (as not all generated a difference)
|
4
|
+
# This class helps to cluster nodes that are related
|
5
|
+
# (then each cluster has as a top only one diff)
|
6
|
+
class ClusteredTreeify
|
7
|
+
class DataIntegrityError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def mode(value)
|
12
|
+
case value
|
13
|
+
when :destination, :curr
|
14
|
+
:curr
|
15
|
+
when :source, :prev
|
16
|
+
:prev
|
17
|
+
else
|
18
|
+
raise ArgumentError, "Unknown mode ':#{value}'"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :diffs, :mode
|
24
|
+
|
25
|
+
# @param on [Symbol] if it treeifies based on :prev (`:source`)
|
26
|
+
# or `:curr` (`:destination`)
|
27
|
+
# - default to `:prev` because it's mostly use to archive
|
28
|
+
def initialize(diffs, mode: :prev)
|
29
|
+
@diffs = diffs
|
30
|
+
@mode = self.class.mode(mode)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Hash] where _key_ is the parent `diff` and _value_ are all
|
34
|
+
# descendants thereof
|
35
|
+
def clusters
|
36
|
+
@clusters = only_present_parents_hash.reject do |_pid, tree|
|
37
|
+
all_children_diffs_hash.key?(tree.id)
|
38
|
+
end.each_with_object({}) do |(_pid, tree), clus|
|
39
|
+
clus[tree.diff] = tree.all_descendants.map(&:diff).uniq
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def all_children_diffs_hash
|
46
|
+
@all_children_diffs_hash ||= all_children_diffs.each_with_object({}) do |node, out|
|
47
|
+
out[node.id] = node.diff
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def all_children_diffs
|
52
|
+
@all_children_diffs ||= only_present_parents_hash.values.map(&:all_descendants).flatten.uniq
|
53
|
+
end
|
54
|
+
|
55
|
+
# wipes out tree nodes that got there just as a token (diff not actually present)
|
56
|
+
def only_present_parents_hash
|
57
|
+
@only_present_parents_hash ||= parents_hash.select do |_pid, tree|
|
58
|
+
tree.diff?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# builds up the hierarchy
|
63
|
+
def parents_hash
|
64
|
+
@parents_hash ||= diffs.each_with_object({}) do |diff, out|
|
65
|
+
id = node_id(diff)
|
66
|
+
pid = parent_id(diff)
|
67
|
+
|
68
|
+
if id == pid
|
69
|
+
%i[unarchive? id? id_name? insert? move? archive?].each do |stage|
|
70
|
+
puts "Stage #{stage}: #{diff.send(stage)}"
|
71
|
+
end
|
72
|
+
msg = "Node '#{id}' seems to be parent of itself"
|
73
|
+
raise DataIntegrityError, msg
|
74
|
+
end
|
75
|
+
|
76
|
+
out[pid] ||= DiffsTree.new(id: pid)
|
77
|
+
out[id] ||= DiffsTree.new(id: id)
|
78
|
+
out[pid].add_child(out[id])
|
79
|
+
out[id].diff = diff
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def parent_id(diff)
|
84
|
+
case mode
|
85
|
+
when :prev
|
86
|
+
diff.parent_id_prev
|
87
|
+
else
|
88
|
+
diff.parent_id
|
89
|
+
end&.upcase
|
90
|
+
end
|
91
|
+
|
92
|
+
def node_id(diff)
|
93
|
+
case mode
|
94
|
+
when :prev
|
95
|
+
diff.node_id_prev
|
96
|
+
else
|
97
|
+
diff.node_id
|
98
|
+
end&.upcase
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|