eco-helpers 2.6.3 → 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 +135 -2
- data/Rakefile +13 -7
- data/eco-helpers.gemspec +3 -3
- 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/ecoportal_api/external_person.rb +10 -6
- data/lib/eco/api/common/version_patches/exception.rb +11 -13
- data/lib/eco/api/error.rb +32 -20
- data/lib/eco/api/microcases/set_supervisor.rb +0 -3
- 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/default_cases/to_csv_detailed_case.rb +0 -2
- 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 +34 -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/input_filters.rb +0 -5
- data/lib/eco/cli_default/people_filters.rb +4 -4
- 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 +54 -9
- data/lib/eco/data/hashes/diff_meta.rb +0 -52
@@ -0,0 +1,72 @@
|
|
1
|
+
module Eco::API::UseCases::GraphQL::Helpers::Location
|
2
|
+
module Base
|
3
|
+
module TreeTracking
|
4
|
+
include Eco::API::UseCases::GraphQL::Helpers::Base::CaseEnv
|
5
|
+
|
6
|
+
TAGTREE_BACKUP = 'cache/tagtree.json'.freeze
|
7
|
+
|
8
|
+
attr_reader :current_tree
|
9
|
+
attr_accessor :previous_tree
|
10
|
+
|
11
|
+
# Allows to override if the @current_tree should be kept
|
12
|
+
# to the latest live version
|
13
|
+
def track_current_tree?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
# Back-up the tree everytime that it is retrieved anew.
|
18
|
+
# @note that what is costly operational is to parse the tree. As it comes
|
19
|
+
# already parsed, we don't lose performance in backing it up.
|
20
|
+
def current_tree=(value)
|
21
|
+
return if current_tree == value
|
22
|
+
return unless value.is_a?(Eco::API::Organization::TagTree)
|
23
|
+
|
24
|
+
@previous_tree = @current_tree
|
25
|
+
@current_tree = value
|
26
|
+
backup_tree(current_tree)
|
27
|
+
end
|
28
|
+
|
29
|
+
# At any moment we want to know how the live tree is
|
30
|
+
# @note it also does a backup
|
31
|
+
# @return [Eco::API::Organization::TagTree] the latest tree (`current_tree`)
|
32
|
+
def track_current_tree(tree)
|
33
|
+
return if simulate?
|
34
|
+
return if tree.nil?
|
35
|
+
|
36
|
+
latest_tree = tree if tree.is_a?(Eco::API::Organization::TagTree)
|
37
|
+
if tree.respond_to?(:treeify) && track_current_tree?
|
38
|
+
latest_tree ||= Eco::API::Organization::TagTree.new(tree.treeify, id: tree.id, name: tree.name)
|
39
|
+
end
|
40
|
+
|
41
|
+
latest_tree.tap do
|
42
|
+
next unless latest_tree.is_a?(Eco::API::Organization::TagTree)
|
43
|
+
next if latest_tree.empty?
|
44
|
+
|
45
|
+
# a backup happens:
|
46
|
+
self.current_tree = latest_tree
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param tree [Eco::API::Organization::TagTree, Hash, Array]
|
51
|
+
# @return [Boolean] whether or not the backup was created
|
52
|
+
def backup_tree(tree = current_tree || live_tree)
|
53
|
+
return false if simulate?
|
54
|
+
case tree
|
55
|
+
when Eco::API::Organization::TagTree
|
56
|
+
tree = tree.source
|
57
|
+
when Hash, Array
|
58
|
+
# that's alright
|
59
|
+
else
|
60
|
+
log(:error) {
|
61
|
+
"Can't back up tagtree. Expecting TagTree, Hash or Array. Given: #{tree.class}"
|
62
|
+
}
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
|
66
|
+
file = session.file_manager.save_json(tree, self.class::TAGTREE_BACKUP, :timestamp)
|
67
|
+
log(:debug) { "Backed-up tagtree saved locally to #{file}." }
|
68
|
+
true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -1,75 +1,28 @@
|
|
1
|
+
require_relative 'base/tree_tracking'
|
2
|
+
require_relative 'base/classifications_parser'
|
1
3
|
module Eco::API::UseCases::GraphQL::Helpers::Location
|
2
4
|
module Base
|
3
5
|
include Eco::API::UseCases::GraphQL::Helpers::Base
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
attr_reader :current_tree
|
8
|
-
attr_accessor :previous_tree
|
9
|
-
|
10
|
-
# Back-up the tree everytime that it is retrieved anew.
|
11
|
-
def current_tree=(value)
|
12
|
-
return current_tree if current_tree == value
|
13
|
-
@current_tree = value
|
14
|
-
backup_tree(current_tree)
|
15
|
-
value
|
16
|
-
end
|
17
|
-
|
18
|
-
# At any moment we want to know how the live tree is
|
19
|
-
# @note it also does a backup
|
20
|
-
# @return [Eco::API::Organization::TagTree] the latest tree (`current_tree`)
|
21
|
-
def track_current_tree(tree)
|
22
|
-
return if simulate?
|
23
|
-
return unless tree
|
24
|
-
latest_tree = tree if tree.is_a?(Eco::API::Organization::TagTree)
|
25
|
-
if tree.respond_to?(:treeify)
|
26
|
-
latest_tree ||= Eco::API::Organization::TagTree.new(tree.treeify, id: tree.id, name: tree.name)
|
27
|
-
end
|
28
|
-
latest_tree.tap do |_tree|
|
29
|
-
next unless latest_tree
|
30
|
-
@previous_tree = @current_tree
|
31
|
-
self.current_tree = latest_tree
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# @param tree [Eco::API::Organization::TagTree, Hash, Array]
|
36
|
-
# @return [Boolean] whether or not the backup was created
|
37
|
-
def backup_tree(tree = current_tree || live_tree)
|
38
|
-
return false if simulate?
|
39
|
-
case tree
|
40
|
-
when Eco::API::Organization::TagTree
|
41
|
-
tagtree = tree.source
|
42
|
-
when Hash, Array
|
43
|
-
# that's allright
|
44
|
-
else
|
45
|
-
log(:error) {
|
46
|
-
"Can't back up tagtree. Expecting TagTree, Hash or Array. Given: #{tree.class}"
|
47
|
-
}
|
48
|
-
return false
|
49
|
-
end
|
50
|
-
file = session.file_manager.save_json(tree, self.class::TAGTREE_BACKUP, :timestamp)
|
51
|
-
logger.debug("Backed up tagtree saved locally to #{file}.")
|
52
|
-
true
|
53
|
-
end
|
54
|
-
|
55
|
-
def tagtree_id
|
56
|
-
%i[target_structure_id tagtree_id structure_id].find {|key| options.dig(:source, key)}
|
57
|
-
end
|
6
|
+
include TreeTracking
|
7
|
+
include ClassificationsParser
|
58
8
|
|
59
9
|
# Scopes the target structure `id`.
|
60
10
|
# @note it is basic that the `id` is correctly identified.
|
61
11
|
def target_structure_id
|
62
12
|
@target_structure_id ||= tagtree_id
|
63
|
-
@target_structure_id ||=
|
64
|
-
@target_structure_id ||= current_tree.id
|
13
|
+
@target_structure_id ||= target_structure_id_const
|
14
|
+
@target_structure_id ||= current_tree.id if current_tree.respond_to?(:id)
|
65
15
|
return @target_structure_id if @target_structure_id
|
16
|
+
|
66
17
|
msg = "Const TARGET_STRUCTURE_ID has not been defined, "
|
67
18
|
msg << "nor options(:source, :structure_id). "
|
68
19
|
msg << "Infering active locations structure."
|
69
20
|
log(:warn) { msg }
|
70
|
-
|
71
|
-
|
72
|
-
|
21
|
+
|
22
|
+
# a backup happens:
|
23
|
+
return nil unless (self.current_tree = session_live_tree)
|
24
|
+
|
25
|
+
@target_structure_id = current_tree.id
|
73
26
|
end
|
74
27
|
|
75
28
|
# Retrieves the live tree only if `current_tree` hasn't been just retrieved.
|
@@ -80,6 +33,8 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
|
|
80
33
|
tree_init = current_tree
|
81
34
|
target_id = target_structure_id
|
82
35
|
return current_tree if current_tree != tree_init
|
36
|
+
|
37
|
+
# a backup happens:
|
83
38
|
self.current_tree = session_live_tree(id: target_id)
|
84
39
|
end
|
85
40
|
|
@@ -88,5 +43,16 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
|
|
88
43
|
def session_live_tree(id: nil)
|
89
44
|
session.live_tree(id: id, include_archived: true)
|
90
45
|
end
|
46
|
+
|
47
|
+
def tagtree_id
|
48
|
+
%i[target_structure_id tagtree_id structure_id].map do |key|
|
49
|
+
options.dig(:source, key)
|
50
|
+
end.compact.first
|
51
|
+
end
|
52
|
+
|
53
|
+
def target_structure_id_const
|
54
|
+
return nil unless self.class.const_defined?(:TARGET_STRUCTURE_ID)
|
55
|
+
self.class.const_get(:TARGET_STRUCTURE_ID)
|
56
|
+
end
|
91
57
|
end
|
92
58
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diff
|
2
|
+
module AsUpdate
|
3
|
+
# Unique access point to generate an update
|
4
|
+
def as_update(operation, after_id_update: false)
|
5
|
+
update_hash = diff_hash(after_id_update: after_id_update)
|
6
|
+
update_hash.slice(*update_attrs(operation))
|
7
|
+
end
|
8
|
+
|
9
|
+
# Adjustments to be able to generate correct updates
|
10
|
+
# @note it transforms the method on a very effective helper
|
11
|
+
def diff_hash(after_id_update: true) # rubocop:disable Metrics/AbcSize
|
12
|
+
super().tap do |h|
|
13
|
+
h.delete('archived')
|
14
|
+
h['classificationIds'] = h.delete('classifications') if h.key?('classifications')
|
15
|
+
|
16
|
+
if archive? || insert?
|
17
|
+
# We assume archives do not have `move` nor update `id` or `name`
|
18
|
+
h['nodeId'] = node_id || node_id_prev
|
19
|
+
h['parentId'] = parent_id if insert?
|
20
|
+
elsif update?
|
21
|
+
if id? && !after_id_update
|
22
|
+
h['nodeId'] = node_id_prev
|
23
|
+
h['newId'] = node_id
|
24
|
+
elsif id? # after id update
|
25
|
+
h['nodeId'] = node_id
|
26
|
+
else # no id? change , and before the id_update stage
|
27
|
+
h['nodeId'] = node_id_prev
|
28
|
+
end
|
29
|
+
if move?
|
30
|
+
h['parentId'] = after_id_update ? parent_id : parent_id_prev
|
31
|
+
end
|
32
|
+
else
|
33
|
+
# Something is wrong (not expected to be any other category)
|
34
|
+
raise "Unexpected condition, please review code base"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# List of attributes that can be part of an operation
|
40
|
+
def update_attrs(operation)
|
41
|
+
['nodeId'].tap do |attrs|
|
42
|
+
plus =
|
43
|
+
case operation
|
44
|
+
when :id_name, :update
|
45
|
+
%w[newId name classificationIds]
|
46
|
+
when :id
|
47
|
+
%w[newId]
|
48
|
+
when :insert
|
49
|
+
%w[name parentId classificationIds]
|
50
|
+
when :move
|
51
|
+
%w[parentId]
|
52
|
+
else
|
53
|
+
[]
|
54
|
+
end
|
55
|
+
attrs.push(*plus)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diff
|
2
|
+
module Compare
|
3
|
+
# Sorts from top to bottom (parents go first)
|
4
|
+
# @note
|
5
|
+
# 1. The analysis below will assume that NO `nodeId` has changed. Which is what
|
6
|
+
# this class helper tries to somehow ensure/emmulate.
|
7
|
+
# 2. The below has been thought for NodesDiff::STAGES order:
|
8
|
+
# [:unarchive, :id_name, :insert, :move, :archive]
|
9
|
+
# @note
|
10
|
+
# - If there was a change of id involved in child or parent, the method used
|
11
|
+
# on each (attr vs prev_attr) could be different. We would need to know at
|
12
|
+
# what stage we are in the general update.
|
13
|
+
# - If MOVE has yet to come, `attr_prev` is alright, as we are interested
|
14
|
+
# in the current parent-to-child order in the locations structure.
|
15
|
+
# UNARCHIVE nodes have `attr_prev` and, therefore, this can be used to sort.
|
16
|
+
# - If MOVE has happened, then `attr_prev` is missleading (order could be different),
|
17
|
+
# so `attr` should be used, which refers to the final parents. However,
|
18
|
+
# ARCHIVE nodes only have `attr_prev`, which could refer to the previous parent. And yet
|
19
|
+
# as there's no current data, they have NOT been moved, which makes `prev_attr` reliable.
|
20
|
+
# @note
|
21
|
+
# - One thing is still to be resolved. `unarchive?` could entail a later `move?` (same NodeDiff).
|
22
|
+
# Therefore, the STAGE when the comparison is done is relevant.
|
23
|
+
# - This has been resolved with `sort_before_move` & `sort_after_move` methods.
|
24
|
+
def <=>(other)
|
25
|
+
if archive? || unarchive?
|
26
|
+
return -1 if node_id_prev == other.parent_id_prev
|
27
|
+
return 1 if parent_id_prev == other.node_id_prev
|
28
|
+
elsif insert? || move?
|
29
|
+
return -1 if node_id == other.parent_id
|
30
|
+
return 1 if parent_id == other.node_id
|
31
|
+
end
|
32
|
+
0
|
33
|
+
end
|
34
|
+
|
35
|
+
def sort_before_move(other)
|
36
|
+
self <=> other
|
37
|
+
end
|
38
|
+
|
39
|
+
def sort_after_move(other)
|
40
|
+
if move?
|
41
|
+
return -1 if node_id == other.parent_id
|
42
|
+
return 1 if parent_id == other.node_id
|
43
|
+
0
|
44
|
+
else
|
45
|
+
self <=> other
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Eco::API::UseCases::GraphQL::Helpers::Location
|
2
|
+
# To prepend to class Custom::UseCase::TagtreeDiff::NodeDiff
|
3
|
+
# Currently, name and id are the same thing, so we make some adjustments.
|
4
|
+
class Command::Diff < Eco::Data::Locations::NodeDiff
|
5
|
+
require_relative 'diff/compare'
|
6
|
+
require_relative 'diff/as_update'
|
7
|
+
|
8
|
+
include Compare
|
9
|
+
include AsUpdate
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
|
2
|
+
module Stages
|
3
|
+
module Commandable
|
4
|
+
class << self
|
5
|
+
def included(base)
|
6
|
+
super
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
base.send(:include, DiffSortable)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def stage_command(stage)
|
14
|
+
return :update if stage == :id_name
|
15
|
+
stage
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# builds up the particular graphql location commands
|
20
|
+
def stage_commands(stage)
|
21
|
+
stage_command = self.class.stage_command(stage)
|
22
|
+
stage_updates(stage).map do |update|
|
23
|
+
{}.tap do |comm|
|
24
|
+
comm[stage_command] = symbolize_keys(update)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def stage_updates(stage)
|
32
|
+
aiu = self.class.after_id_update?(stage)
|
33
|
+
stage_diffs(stage).map do |dfs|
|
34
|
+
dfs.as_update(stage, after_id_update: aiu)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def stage_diffs(stage)
|
39
|
+
raise "There is NO support for stage '#{stage}'. Please review" unless respond_to?(stage)
|
40
|
+
sort_diffs(stage) do
|
41
|
+
send(stage)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_archive.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
|
2
|
+
module Stages
|
3
|
+
module DiffSortable
|
4
|
+
module ForArchive
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def only_first_ancestor_in_chains(raw_diffs)
|
9
|
+
parents = raw_diffs.each_with_object({}) do |dff, prs|
|
10
|
+
(prs[dff.parent_id_prev] ||= []) << dff
|
11
|
+
end
|
12
|
+
|
13
|
+
present_parent_prev_ids = raw_diffs.select do |dff|
|
14
|
+
parents.key?(dff.node_id_prev)
|
15
|
+
end.map(&:node_id_prev)
|
16
|
+
|
17
|
+
children_with_parents = parents.values_at(*present_parent_prev_ids).flatten(1).uniq
|
18
|
+
raw_diffs - children_with_parents
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
|
2
|
+
module Stages
|
3
|
+
module DiffSortable
|
4
|
+
module ForUnarchive
|
5
|
+
# we require the original tree to be able to track what ancestors
|
6
|
+
# should be unarchived, with the aim of unarchiving a node that
|
7
|
+
# is target of update (i.e. revive and move somewhere else)
|
8
|
+
def original_tree
|
9
|
+
defined?(super)? super : @original_tree
|
10
|
+
end
|
11
|
+
|
12
|
+
def original_tree_node(id)
|
13
|
+
original_tree.node(id)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Uses method of core class ArrayDiff
|
19
|
+
def results_by_node_id_prev
|
20
|
+
@results_by_node_id_prev ||= source_results.each_with_object({}) do |diff_result, out|
|
21
|
+
out[diff_result.node_id_prev] = diff_result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Generates result diffs where archied ancestors are being unarchived
|
26
|
+
# It ensures unique diff results for nodeId where `raw_diffs` takes precedence
|
27
|
+
def include_unarchive_of_archived_ancestors(raw_diffs)
|
28
|
+
ancestors_diffs = raw_diffs.each_with_object([]) do |diff_result, out|
|
29
|
+
ans_diff_results = archived_ancestors_as_unarchive_diffs(diff_result.node_id_prev)
|
30
|
+
out.concat(ans_diff_results)
|
31
|
+
end
|
32
|
+
(raw_diffs + ancestors_diffs).uniq(&:node_id_prev)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Unarchive comes with a bad joke. You need to unarchive archived parents
|
36
|
+
# even if they should remain archived.
|
37
|
+
def archived_ancestors_as_unarchive_diffs(id)
|
38
|
+
archived_tree_node_ancestors(id).map do |node|
|
39
|
+
if (dff = results_by_node_id_prev[node.id])
|
40
|
+
# generate a node diff with no changes
|
41
|
+
dff.dup(src_2: dff.src_1).tap do |nres|
|
42
|
+
nres.src_2['archived'] = false
|
43
|
+
end
|
44
|
+
else
|
45
|
+
# Some trouble -> can re-use
|
46
|
+
puts "Couldn't find diff_res for archived ancestor '#{node.id}'"
|
47
|
+
end
|
48
|
+
end.compact
|
49
|
+
end
|
50
|
+
|
51
|
+
def archived_tree_node_ancestors(id)
|
52
|
+
original_tree_node_ancestors(id).select(&:archived)
|
53
|
+
end
|
54
|
+
|
55
|
+
def original_tree_node_ancestors(id)
|
56
|
+
if (node = original_tree_node(id))
|
57
|
+
node.ancestors
|
58
|
+
else
|
59
|
+
[]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative 'diff_sortable/for_unarchive'
|
2
|
+
require_relative 'diff_sortable/for_archive'
|
3
|
+
|
4
|
+
class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
|
5
|
+
module Stages
|
6
|
+
module DiffSortable
|
7
|
+
class << self
|
8
|
+
def included(base)
|
9
|
+
super
|
10
|
+
base.send(:include, Sortable)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
include ForUnarchive
|
15
|
+
include ForArchive
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# @yield
|
20
|
+
# @yieldreturn [Array<Diff>] the raw diffs of `stage`
|
21
|
+
def sort_diffs(stage)
|
22
|
+
raise ArgumentError, "Expecting block but not given" unless block_given?
|
23
|
+
raw_diffs = yield
|
24
|
+
|
25
|
+
case stage
|
26
|
+
when :archive
|
27
|
+
only_first_ancestor_in_chains(sort_top_to_bottom(raw_diffs, stage))
|
28
|
+
when :unarchive
|
29
|
+
raw_diffs = include_unarchive_of_archived_ancestors(raw_diffs)
|
30
|
+
sort_top_to_bottom(uniq_archive_token(raw_diffs, stage), stage)
|
31
|
+
when :move
|
32
|
+
sort_bottom_to_top(raw_diffs, stage)
|
33
|
+
else
|
34
|
+
sort_top_to_bottom(raw_diffs, stage)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# It should also include archived ancestors !!
|
39
|
+
# @note this will translate to the unarchive of a wider range of nodes.
|
40
|
+
# - Please notice that the design is correct (there's no way around it)
|
41
|
+
def uniq_archive_token(raw_diffs, stage)
|
42
|
+
raw_diffs.group_by(&:archived_token_prev).each_with_object([]) do |(token, token_diffs), out|
|
43
|
+
next out.concat(token_diffs) unless token
|
44
|
+
out << sort_top_to_bottom(token_diffs, stage).first
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
|
2
|
+
module Stages
|
3
|
+
module Sortable
|
4
|
+
# Class that provided elements that may have a relationship
|
5
|
+
# with a provided `block` that compares two single elements (`<==>`)
|
6
|
+
# it returns the sorted list of the elements in a safe way
|
7
|
+
# and by keeping the original order when `<==>` returns `0`
|
8
|
+
class RelationSafeSort
|
9
|
+
attr_reader :list
|
10
|
+
attr_reader :cluster_mode
|
11
|
+
|
12
|
+
# @param cluster_mode [Boolean] whether it should optimize by trying to
|
13
|
+
# first treeify and only trying to resolve the by pairings (one to one)
|
14
|
+
# the diffs that are hanging from now where.
|
15
|
+
# Possible values are `:prev` or `:curr`
|
16
|
+
def initialize(list, cluster_mode: nil, &block)
|
17
|
+
@list = list
|
18
|
+
@cluster_mode = clusterer_class.mode(cluster_mode) if cluster_mode
|
19
|
+
@comparer = block
|
20
|
+
end
|
21
|
+
|
22
|
+
def clustered?
|
23
|
+
!!@cluster_mode
|
24
|
+
end
|
25
|
+
|
26
|
+
# @yield [a, b] elements to be sorted
|
27
|
+
# When the comparer `<=>` returns `0`, it still ensures the order is correct
|
28
|
+
# Please notice that native `sort` is not stable when `<=>` can return `0`
|
29
|
+
def sort
|
30
|
+
return list.dup if list.count <= 1
|
31
|
+
|
32
|
+
# treeifying we ensure correct order (parent, children)
|
33
|
+
pending = parents_hash.dup
|
34
|
+
treeify = proc do |item|
|
35
|
+
next item unless (children = pending.delete(item))
|
36
|
+
{item => children.map {|child| treeify.call(child)}}
|
37
|
+
end
|
38
|
+
|
39
|
+
sorted_tree = top_parents.each_with_object(unpaired) do |item, tree|
|
40
|
+
next unless pending.key?(item)
|
41
|
+
tree << treeify.call(item)
|
42
|
+
end
|
43
|
+
|
44
|
+
flatten(sorted_tree)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :comparer
|
50
|
+
|
51
|
+
def compare_two(a, b)
|
52
|
+
comparer.call(a, b)
|
53
|
+
end
|
54
|
+
|
55
|
+
def unpaired
|
56
|
+
list - paired
|
57
|
+
end
|
58
|
+
|
59
|
+
def paired
|
60
|
+
parents_hash.keys | all_children
|
61
|
+
end
|
62
|
+
|
63
|
+
def parents_hash
|
64
|
+
@parents_hash ||=
|
65
|
+
if clustered?
|
66
|
+
clusterer.clusters
|
67
|
+
else
|
68
|
+
paired_related_nodes.group_by(&:shift).transform_values(&:flatten)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def clusterer
|
73
|
+
@clusterer ||= clusterer_class.new(list, mode: cluster_mode)
|
74
|
+
end
|
75
|
+
|
76
|
+
def top_parents
|
77
|
+
@top_parents ||= parents_hash.keys.reject {|item| all_children.include?(item)}
|
78
|
+
end
|
79
|
+
|
80
|
+
def all_children
|
81
|
+
@all_children ||= parents_hash.values.flatten(1).uniq
|
82
|
+
end
|
83
|
+
|
84
|
+
# @yield [a, b] elements to be sorted
|
85
|
+
# Pairs one to one relationships in order
|
86
|
+
# @note on relationship, parent comes first
|
87
|
+
def paired_related_nodes
|
88
|
+
paired_combinations.each_with_object([]) do |pair, chained|
|
89
|
+
next if (result = compare_two(*pair)).zero?
|
90
|
+
chained << (result.negative?? pair : pair.reverse)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def paired_combinations
|
95
|
+
list.combination(2).to_a
|
96
|
+
end
|
97
|
+
|
98
|
+
# Flattens the tree into an Array
|
99
|
+
def flatten(value)
|
100
|
+
case value
|
101
|
+
when Array
|
102
|
+
value.each_with_object([]) do |child, out|
|
103
|
+
out.concat(flatten(child))
|
104
|
+
end.uniq
|
105
|
+
when Hash
|
106
|
+
item, children = value.first
|
107
|
+
[item].concat(flatten(children)).uniq
|
108
|
+
else
|
109
|
+
[value]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def clusterer_class
|
114
|
+
Eco::Data::Locations::NodeDiff::NodesDiff::ClusteredTreeify
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative 'sortable/relation_safe_sort'
|
2
|
+
|
3
|
+
class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
|
4
|
+
module Stages
|
5
|
+
module Sortable
|
6
|
+
# from what diffs size we should use the cluster mode
|
7
|
+
CLUSTER_MODE_FROM = 400
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def included(base)
|
11
|
+
super
|
12
|
+
base.extend(Stages)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def diffs_sort(list, cluster_mode: nil, &block)
|
19
|
+
RelationSafeSort.new(list, cluster_mode: cluster_mode, &block).sort
|
20
|
+
end
|
21
|
+
|
22
|
+
# @note As can't update archived nodes, it happens before :id update (:prev),
|
23
|
+
# and therefore it should use `:prev` rather than `:curr`
|
24
|
+
# @note you wouldn't be able to unarchive a node that does not exist.
|
25
|
+
# Therefore, it does have `:prev`
|
26
|
+
# @see spaceship operator `self.class#<=>`
|
27
|
+
def cluster_mode(stage)
|
28
|
+
case stage
|
29
|
+
when :archive, :unarchive
|
30
|
+
:prev
|
31
|
+
when :insert
|
32
|
+
:curr
|
33
|
+
else
|
34
|
+
return :curr if self.class.after_id_update?(stage)
|
35
|
+
:prev
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def sort_top_to_bottom(raw_diffs, stage)
|
40
|
+
clustered = nil
|
41
|
+
clustered = cluster_mode(stage) if raw_diffs.count > CLUSTER_MODE_FROM
|
42
|
+
|
43
|
+
if self.class.during_or_after_move?(stage)
|
44
|
+
diffs_sort(raw_diffs, cluster_mode: clustered) do |a, b|
|
45
|
+
a.sort_after_move(b)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
diffs_sort(raw_diffs, cluster_mode: clustered) do |a, b|
|
49
|
+
a.sort_before_move(b)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def sort_bottom_to_top(raw_diffs, stage)
|
55
|
+
sort_top_to_bottom(raw_diffs, stage).reverse
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|