eco-helpers 2.0.18 → 2.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +80 -1
- data/eco-helpers.gemspec +4 -1
- data/lib/eco/api/common/base_loader.rb +9 -5
- data/lib/eco/api/common/loaders/parser.rb +1 -0
- data/lib/eco/api/common/people/default_parsers.rb +1 -0
- data/lib/eco/api/common/people/default_parsers/xls_parser.rb +53 -0
- data/lib/eco/api/common/people/entries.rb +1 -0
- data/lib/eco/api/common/people/entry_factory.rb +88 -23
- data/lib/eco/api/common/people/person_entry.rb +1 -0
- data/lib/eco/api/common/people/person_parser.rb +1 -1
- data/lib/eco/api/common/session.rb +1 -0
- data/lib/eco/api/common/session/base_session.rb +2 -0
- data/lib/eco/api/common/session/helpers.rb +30 -0
- data/lib/eco/api/common/session/helpers/prompt_user.rb +34 -0
- data/lib/eco/api/common/version_patches/ecoportal_api/external_person.rb +1 -1
- data/lib/eco/api/common/version_patches/ecoportal_api/internal_person.rb +7 -4
- data/lib/eco/api/common/version_patches/exception.rb +5 -2
- data/lib/eco/api/microcases/with_each.rb +67 -6
- data/lib/eco/api/microcases/with_each_present.rb +4 -2
- data/lib/eco/api/microcases/with_each_starter.rb +4 -2
- data/lib/eco/api/organization.rb +1 -1
- data/lib/eco/api/organization/people.rb +94 -25
- data/lib/eco/api/organization/people_similarity.rb +272 -0
- data/lib/eco/api/organization/person_schemas.rb +5 -1
- data/lib/eco/api/organization/policy_groups.rb +5 -1
- data/lib/eco/api/organization/tag_tree.rb +33 -0
- data/lib/eco/api/session.rb +19 -8
- data/lib/eco/api/session/batch.rb +7 -5
- data/lib/eco/api/session/batch/job.rb +34 -9
- data/lib/eco/api/usecases.rb +2 -2
- data/lib/eco/api/usecases/base_case.rb +2 -2
- data/lib/eco/api/usecases/base_io.rb +17 -4
- data/lib/eco/api/usecases/default_cases.rb +1 -0
- data/lib/eco/api/usecases/default_cases/analyse_people_case.rb +179 -32
- data/lib/eco/api/usecases/default_cases/clean_unknown_tags_case.rb +37 -0
- data/lib/eco/api/usecases/default_cases/to_csv_case.rb +81 -36
- data/lib/eco/api/usecases/default_cases/to_csv_detailed_case.rb +3 -4
- data/lib/eco/api/usecases/ooze_samples/ooze_update_case.rb +3 -2
- data/lib/eco/cli/config/default/input.rb +61 -8
- data/lib/eco/cli/config/default/options.rb +47 -2
- data/lib/eco/cli/config/default/people.rb +18 -24
- data/lib/eco/cli/config/default/usecases.rb +33 -2
- data/lib/eco/cli/config/default/workflow.rb +12 -7
- data/lib/eco/cli/scripting/args_helpers.rb +2 -2
- data/lib/eco/csv.rb +4 -2
- data/lib/eco/csv/table.rb +121 -21
- data/lib/eco/data/fuzzy_match.rb +109 -27
- data/lib/eco/data/fuzzy_match/chars_position_score.rb +3 -2
- data/lib/eco/data/fuzzy_match/ngrams_score.rb +19 -10
- data/lib/eco/data/fuzzy_match/pairing.rb +12 -19
- data/lib/eco/data/fuzzy_match/result.rb +22 -2
- data/lib/eco/data/fuzzy_match/results.rb +30 -6
- data/lib/eco/data/fuzzy_match/score.rb +12 -7
- data/lib/eco/data/fuzzy_match/string_helpers.rb +14 -1
- data/lib/eco/version.rb +1 -1
- metadata +67 -3
- data/lib/eco/api/organization/people_analytics.rb +0 -60
@@ -44,7 +44,11 @@ module Eco
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def policy_group(id_name)
|
47
|
-
|
47
|
+
self[id_name]
|
48
|
+
end
|
49
|
+
|
50
|
+
def [](id_name)
|
51
|
+
@by_id[policy_group_id(id_name)]
|
48
52
|
end
|
49
53
|
|
50
54
|
def user_pg_ids(initial: [], final: [], non_custom: (non_custom_not_used = true; []), preserve_custom: true)
|
@@ -42,6 +42,39 @@ module Eco
|
|
42
42
|
init_hashes
|
43
43
|
end
|
44
44
|
|
45
|
+
# Updates the tag of the current tree
|
46
|
+
def tag=(value)
|
47
|
+
@tag = value
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [Eco::API::Organization::TagTree]
|
51
|
+
def dup
|
52
|
+
self.class.new(as_json)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Array] with the differences
|
56
|
+
def diff(tagtree, differences: {}, level: 0, **options)
|
57
|
+
require 'hashdiff'
|
58
|
+
Hashdiff.diff(self.as_json, tagtree.as_json, **options.slice(:array_path, :similarity, :use_lcs))
|
59
|
+
end
|
60
|
+
|
61
|
+
def top?
|
62
|
+
depth == -1
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Array[Hash]] where `Hash` is a `node` `{"tag" => TAG, "nodes": Array[Hash]}`
|
66
|
+
def as_json
|
67
|
+
nodes_json = nodes.map {|node| node.as_json}
|
68
|
+
if top?
|
69
|
+
nodes_json
|
70
|
+
else
|
71
|
+
{
|
72
|
+
"tag" => tag,
|
73
|
+
"nodes" => nodes_json
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
45
78
|
# @return [Boolean] `true` if there are tags in the node, `false` otherwise.
|
46
79
|
def empty?
|
47
80
|
@has_tags.empty?
|
data/lib/eco/api/session.rb
CHANGED
@@ -66,6 +66,16 @@ module Eco
|
|
66
66
|
@presets_factory ||= Eco::API::Organization::PresetsFactory.new(enviro: enviro)
|
67
67
|
end
|
68
68
|
|
69
|
+
# @return [Eco::Data::Mapper] the mappings between the internal and external attribute/property names.
|
70
|
+
def fields_mapper
|
71
|
+
return @fields_mapper if instance_variable_defined?(:@fields_mapper)
|
72
|
+
mappings = []
|
73
|
+
if map_file = config.people.fields_mapper
|
74
|
+
mappings = map_file ? file_manager.load_json(map_file) : []
|
75
|
+
end
|
76
|
+
@fields_mapper = Eco::Data::Mapper.new(mappings)
|
77
|
+
end
|
78
|
+
|
69
79
|
# Helper to obtain a EntryFactory
|
70
80
|
# @param schema [String, Ecoportal::API::V1::PersonSchema] `schema` to which associate the EntryFactory,
|
71
81
|
# where `String` can be the _name_ or the _id_ of the schema.
|
@@ -74,17 +84,16 @@ module Eco
|
|
74
84
|
def entry_factory(schema: nil)
|
75
85
|
schema = to_schema(schema) || self.schema
|
76
86
|
return @entry_factories[schema&.id] if @entry_factories.key?(schema&.id)
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
mappings = map_file ? file_manager.load_json(map_file) : []
|
87
|
+
unless @entry_factories.empty?
|
88
|
+
@entry_factories[schema&.id] = @entry_factories.values.first.newFactory(schema: schema)
|
89
|
+
return @entry_factories[schema&.id]
|
81
90
|
end
|
82
91
|
|
83
92
|
@entry_factories[schema&.id] = Eco::API::Common::People::EntryFactory.new(
|
84
93
|
enviro,
|
85
94
|
schema: schema,
|
86
95
|
person_parser: config.people.parser,
|
87
|
-
attr_map:
|
96
|
+
attr_map: fields_mapper
|
88
97
|
)
|
89
98
|
end
|
90
99
|
|
@@ -103,11 +112,13 @@ module Eco
|
|
103
112
|
# @param attr [String] type (`Symbol`) or attribute (`String`) to target a specific parser.
|
104
113
|
# @param source [Any] source value to be parsed.
|
105
114
|
# @param phase [Symbol] the phase when this parser should be active.
|
106
|
-
|
115
|
+
# @param phase [Symbol] the phase when this parser should be active.
|
116
|
+
# @return [Object] the parsed attribute.
|
117
|
+
def parse_attribute(attr, source, phase = :internal, deps: {})
|
107
118
|
unless parsers = entry_factory.person_parser
|
108
119
|
raise "There are no parsers defined"
|
109
120
|
end
|
110
|
-
parsers.parse(attr, source, phase)
|
121
|
+
parsers.parse(attr, source, phase, deps: deps)
|
111
122
|
end
|
112
123
|
|
113
124
|
# @see Eco::API::Common::People::EntryFactory#export
|
@@ -127,7 +138,7 @@ module Eco
|
|
127
138
|
# @see Eco::API::Common::People::EntryFactory#new
|
128
139
|
# @return [Eco::API::Common::People::PersonEntry] parsed entry.
|
129
140
|
def new_entry(data, dependencies: {})
|
130
|
-
entry_factory.new(data, dependencies: dependencies)
|
141
|
+
entry_factory(schema: data&.details&.schema_id).new(data, dependencies: dependencies)
|
131
142
|
end
|
132
143
|
|
133
144
|
# @see Eco::API::Common::People::EntryFactory#entries
|
@@ -136,11 +136,13 @@ module Eco
|
|
136
136
|
block.call
|
137
137
|
rescue error_type => e
|
138
138
|
raise unless retries_left > 0
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
139
|
+
explanation = "Batch TimeOut. You have #{retries_left} retries left."
|
140
|
+
prompt_user("Do you want to retry (y/N)?", explanation, default: "Y", timeout: 10) do |response|
|
141
|
+
if response.upcase.start_with?("Y")
|
142
|
+
offer_retry_on(error_type, retries_left - 1, &block)
|
143
|
+
else
|
144
|
+
raise
|
145
|
+
end
|
144
146
|
end
|
145
147
|
end
|
146
148
|
end
|
@@ -164,12 +164,17 @@ module Eco
|
|
164
164
|
# @return [Eco::API::Session::Batch::Status]
|
165
165
|
def launch(simulate: false)
|
166
166
|
pqueue = processed_queue
|
167
|
-
@requests =
|
167
|
+
@requests = as_update(pqueue)
|
168
168
|
pre_checks(requests, simulate: simulate)
|
169
169
|
|
170
|
-
|
170
|
+
if simulate
|
171
|
+
if options.dig(:requests, :backup)
|
172
|
+
req_backup = as_update(pqueue, add_feedback: false)
|
173
|
+
backup_update(req_backup, simulate: simulate)
|
174
|
+
end
|
175
|
+
else
|
171
176
|
if pqueue.length > 0
|
172
|
-
req_backup =
|
177
|
+
req_backup = as_update(pqueue, add_feedback: false)
|
173
178
|
backup_update(req_backup)
|
174
179
|
session.batch.launch(pqueue, method: type).tap do |job_status|
|
175
180
|
@status = job_status
|
@@ -220,13 +225,26 @@ module Eco
|
|
220
225
|
end.join("\n")
|
221
226
|
end
|
222
227
|
|
223
|
-
def as_update(*args)
|
224
|
-
|
228
|
+
def as_update(data, *args)
|
229
|
+
if data.is_a?(Array)
|
230
|
+
data.map do |e|
|
231
|
+
feedback.as_update(e, *args)
|
232
|
+
end.compact.select {|e| e && !e.empty?}
|
233
|
+
else
|
234
|
+
feedback.as_update(data, *args)
|
235
|
+
end
|
225
236
|
end
|
226
237
|
|
227
238
|
def processed_queue
|
228
239
|
@queue.each {|e| @callbacks[e].call(e) if @callbacks.key?(e) }
|
229
|
-
apply_policies(api_included(@queue)).select
|
240
|
+
apply_policies(api_included(@queue)).select do |e|
|
241
|
+
!as_update(e).empty?
|
242
|
+
end.select do |e|
|
243
|
+
next true unless e.is_a?(Ecoportal::API::V1::Person)
|
244
|
+
next true unless e.new?
|
245
|
+
# new people should either have account or details
|
246
|
+
e.account || e.details
|
247
|
+
end
|
230
248
|
end
|
231
249
|
|
232
250
|
# if there is a config definition to exclude entries
|
@@ -235,7 +253,13 @@ module Eco
|
|
235
253
|
def api_included(full_queue)
|
236
254
|
return full_queue if type == :create
|
237
255
|
return full_queue unless excluded = session.config.people.api_excluded
|
238
|
-
|
256
|
+
if options.dig(:include, :only_excluded)
|
257
|
+
full_queue.select {|entry| excluded.call(entry, session, options, self)}
|
258
|
+
elsif options.dig(:include, :excluded)
|
259
|
+
full_queue
|
260
|
+
else
|
261
|
+
full_queue.select {|entry| !excluded.call(entry, session, options, self)}
|
262
|
+
end
|
239
263
|
end
|
240
264
|
|
241
265
|
# Applies the changes introduced by api policies
|
@@ -307,9 +331,10 @@ module Eco
|
|
307
331
|
end
|
308
332
|
|
309
333
|
# Keep a copy of the requests for future reference
|
310
|
-
def backup_update(requests)
|
334
|
+
def backup_update(requests, simulate: false)
|
335
|
+
dry_run = simulate ? "_dry_run" : ""
|
311
336
|
dir = config.people.requests_folder
|
312
|
-
file = File.join(dir, "#{type}_data.json")
|
337
|
+
file = File.join(dir, "#{type}_data#{dry_run}.json")
|
313
338
|
file_manager.save_json(requests, file, :timestamp)
|
314
339
|
end
|
315
340
|
|
data/lib/eco/api/usecases.rb
CHANGED
@@ -2,7 +2,7 @@ module Eco
|
|
2
2
|
module API
|
3
3
|
class UseCases
|
4
4
|
|
5
|
-
class UnkownCase <
|
5
|
+
class UnkownCase < StandardError
|
6
6
|
def initialize(msg = nil, case_name: nil, type: nil)
|
7
7
|
msg ||= "Unkown case"
|
8
8
|
msg += ". Case name '#{case_name}'" if case_name
|
@@ -11,7 +11,7 @@ module Eco
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
class AmbiguousCaseReference <
|
14
|
+
class AmbiguousCaseReference < StandardError
|
15
15
|
def initialize(msg = nil, case_name: nil)
|
16
16
|
msg ||= "You must specify type when there are multiple cases with same name"
|
17
17
|
msg += ". Case name '#{case_name}'" if case_name
|
@@ -4,7 +4,7 @@ module Eco
|
|
4
4
|
# Core class of UseCases. It basically defines and manages allowed `types`
|
5
5
|
class BaseCase
|
6
6
|
|
7
|
-
class InvalidType <
|
7
|
+
class InvalidType < StandardError
|
8
8
|
def initialize(msg = nil, type:, types:)
|
9
9
|
msg ||= "Invalid type."
|
10
10
|
msg = "Given type '#{type}'. Valid types: #{types}"
|
@@ -13,7 +13,7 @@ module Eco
|
|
13
13
|
end
|
14
14
|
|
15
15
|
extend Eco::API::Common::ClassHelpers
|
16
|
-
|
16
|
+
|
17
17
|
@types = [:import, :filter, :transform, :sync, :error_handler, :export, :other]
|
18
18
|
|
19
19
|
class << self
|
@@ -5,6 +5,19 @@ module Eco
|
|
5
5
|
class BaseIO < BaseCase
|
6
6
|
@types = BaseCase.types
|
7
7
|
|
8
|
+
class MissingParameter < StandardError
|
9
|
+
attr_reader :type, :required, :given
|
10
|
+
|
11
|
+
def initialize(msg = nil, type: nil, required:, given:)
|
12
|
+
@type = type
|
13
|
+
@required = required
|
14
|
+
@given = given
|
15
|
+
msg += " of type '#{type}'" if type
|
16
|
+
msg += " requires an object '#{required}'. Given: #{given}."
|
17
|
+
super(msg)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
8
21
|
class << self
|
9
22
|
def input_required?(type)
|
10
23
|
!valid_type?(type) || [:import, :sync].include?(type)
|
@@ -80,13 +93,13 @@ module Eco
|
|
80
93
|
def validate_args(input:, people:, session:, options:)
|
81
94
|
case
|
82
95
|
when !session.is_a?(Eco::API::Session)
|
83
|
-
raise "
|
96
|
+
raise MissingParameter.new("UseCase", required: :session, given: session.class)
|
84
97
|
when input_required? && !input
|
85
|
-
raise "UseCase
|
98
|
+
raise MissingParameter.new("UseCase", type: type, required: :input, given: input.class)
|
86
99
|
when people_required? && !people.is_a?(Eco::API::Organization::People)
|
87
|
-
raise "UseCase
|
100
|
+
raise MissingParameter.new("UseCase", type: type, required: :people, given: people.class)
|
88
101
|
when !options || (options && !options.is_a?(Hash))
|
89
|
-
raise "
|
102
|
+
raise MissingParameter.new("Use Case options", required: :Hash, given: options.class)
|
90
103
|
end
|
91
104
|
true
|
92
105
|
end
|
@@ -13,6 +13,7 @@ require_relative 'default_cases/abstract_policygroup_abilities_case.rb'
|
|
13
13
|
require_relative 'default_cases/analyse_people_case'
|
14
14
|
require_relative 'default_cases/append_usergroups_case'
|
15
15
|
require_relative 'default_cases/change_email_case'
|
16
|
+
require_relative 'default_cases/clean_unknown_tags_case'
|
16
17
|
require_relative 'default_cases/codes_to_tags_case'
|
17
18
|
require_relative 'default_cases/create_case'
|
18
19
|
require_relative 'default_cases/create_details_case'
|
@@ -5,71 +5,218 @@ class Eco::API::UseCases::DefaultCases::AnalysePeople < Eco::API::Common::Loader
|
|
5
5
|
attr_reader :session, :people, :options
|
6
6
|
|
7
7
|
def main(people, session, options, usecase)
|
8
|
+
options[:end_get] = false
|
8
9
|
@session = session; @options = options; @people = people
|
9
10
|
|
10
|
-
|
11
|
+
case
|
12
|
+
when case_options[:identify_duplicates]
|
13
|
+
identify_duplicates
|
14
|
+
when case_options[:identify_unnamed]
|
15
|
+
identify_unnamed
|
16
|
+
else
|
17
|
+
session.logger.info("No analysis operation was specified")
|
18
|
+
end.tap do |people_involved|
|
19
|
+
if people_involved
|
20
|
+
to_csv(people_involved) if to_csv?
|
21
|
+
create_people_backup(people_involved) if results_people_backup?
|
22
|
+
end
|
23
|
+
end
|
11
24
|
end
|
12
25
|
|
13
26
|
private
|
14
27
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
28
|
+
def identify_unnamed
|
29
|
+
similarity_analytics.unnamed.tap do |unnamed|
|
30
|
+
if unnamed.empty?
|
31
|
+
session.logger.info("There were no people with no name!!")
|
32
|
+
end
|
33
|
+
end
|
18
34
|
end
|
19
35
|
|
20
|
-
def
|
21
|
-
|
36
|
+
def identify_duplicates
|
37
|
+
analysed = similarity_screening
|
38
|
+
if case_options[:ignore_matching_words]
|
39
|
+
puts "Fine tune results by ignoring matching words..."
|
40
|
+
analysed = strict_similarity(analysed)
|
41
|
+
end
|
42
|
+
|
43
|
+
similarity_analytics.newSimilarity(analysed).tap do |related_people|
|
44
|
+
if related_people.empty?
|
45
|
+
session.logger.info("There were no possible duplicates identified!!")
|
46
|
+
else
|
47
|
+
report = similarity_analytics.report(analysed, format: :txt)
|
48
|
+
save!(report)
|
49
|
+
end
|
50
|
+
end
|
22
51
|
end
|
23
52
|
|
24
|
-
def
|
25
|
-
|
53
|
+
def strict_similarity(analysed)
|
54
|
+
similarity_analytics.ignore_matching_words(analysed, **{
|
55
|
+
threshold: 0.5,
|
56
|
+
order: [:ngrams]
|
57
|
+
})
|
26
58
|
end
|
27
59
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
60
|
+
def similarity_screening
|
61
|
+
similarity_analytics.attribute = field_similarity
|
62
|
+
options = {
|
63
|
+
threshold: 0.4,
|
64
|
+
order: [:average, :dice]
|
65
|
+
}.tap do |opts|
|
66
|
+
opts.merge!(needle_read: facet_field_proc) if facet_field?
|
67
|
+
opts.merge!(unique_words: true) if unique_words?
|
68
|
+
end
|
69
|
+
analysed = similarity_analytics.analyse(**options)
|
70
|
+
puts "Got #{analysed.count} results after basic screening with #{options}"
|
71
|
+
|
72
|
+
return analysed if case_options[:only_screening]
|
73
|
+
options = {threshold: 0.5, order: [:average]}
|
74
|
+
puts "Going to rearrange results... with #{options}"
|
75
|
+
similarity_analytics.rearrange(analysed, **options).tap do |analysed|
|
76
|
+
puts "... got #{analysed.count} results after rearranging"
|
32
77
|
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def similarity_analytics
|
81
|
+
@analytics ||= people.similarity
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_people_backup(cut = people, file = results_people_backup)
|
85
|
+
session.file_manager.save_json(cut, file)
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_csv(data = people, file = csv_file)
|
89
|
+
opts = {}
|
90
|
+
opts.deep_merge!(export: {file: {name: file, format: :csv}})
|
91
|
+
opts.deep_merge!(export: {options: {nice_header: true}})
|
92
|
+
opts.deep_merge!(export: {options: {internal_names: true}})
|
93
|
+
#opts.deep_merge!(export: {options: {split_schemas: true}})
|
94
|
+
session.process_case("to-csv", type: :export, people: data, options: opts.merge(options.slice(:export)))
|
95
|
+
end
|
96
|
+
|
97
|
+
def unique_words?
|
98
|
+
case_options[:unique_words]
|
99
|
+
end
|
100
|
+
|
101
|
+
def field_similarity
|
102
|
+
return :name unless use_field?
|
103
|
+
use_field_proc
|
104
|
+
end
|
105
|
+
|
106
|
+
def use_field_proc
|
107
|
+
proc_value_access(use_field)
|
108
|
+
end
|
109
|
+
|
110
|
+
def facet_field_proc
|
111
|
+
proc_value_access(facet_field)
|
112
|
+
end
|
113
|
+
|
114
|
+
def use_field
|
115
|
+
case_options.dig(:use_field)
|
116
|
+
end
|
33
117
|
|
34
|
-
|
118
|
+
def use_field?
|
119
|
+
!!use_field
|
120
|
+
end
|
35
121
|
|
36
|
-
|
122
|
+
def facet_field
|
123
|
+
case_options.dig(:facet_field)
|
124
|
+
end
|
125
|
+
|
126
|
+
def facet_field?
|
127
|
+
!!facet_field
|
128
|
+
end
|
129
|
+
|
130
|
+
def csv_file
|
131
|
+
case_options.dig(:csv_file)
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_csv?
|
135
|
+
!!csv_file
|
136
|
+
end
|
137
|
+
|
138
|
+
def results_people_backup
|
139
|
+
case_options.dig(:backup_people)
|
140
|
+
end
|
141
|
+
|
142
|
+
def results_people_backup?
|
143
|
+
!!results_people_backup
|
144
|
+
end
|
145
|
+
|
146
|
+
def case_options
|
147
|
+
options.dig(:usecase, :analyse_people) || {}
|
148
|
+
end
|
149
|
+
|
150
|
+
def output_file
|
151
|
+
@output_file ||= options.dig(:output, :file) || "analytics.txt"
|
152
|
+
end
|
153
|
+
|
154
|
+
def save!(data)
|
155
|
+
ext = File.extname(output_file).downcase.delete(".")
|
156
|
+
session.logger.info("Generating file '#{output_file}'")
|
157
|
+
File.open(output_file, "w") do |fd|
|
37
158
|
if ext == "txt"
|
38
|
-
|
159
|
+
fd << data
|
39
160
|
elsif ext == "html"
|
40
161
|
puts "html is still not supported"
|
41
162
|
exit(1)
|
42
|
-
create_file(data, file: file, format: :html)
|
43
163
|
elsif ext == "json"
|
44
164
|
puts "json is still not supported"
|
45
165
|
exit(1)
|
46
|
-
create_file(data, file: file, format: :json)
|
47
166
|
end
|
48
167
|
end
|
49
168
|
end
|
50
169
|
|
51
|
-
|
52
|
-
|
53
|
-
|
170
|
+
# A way to use command line to specify part
|
171
|
+
# => i.e. details[first-name] AND details[surname]
|
172
|
+
def proc_value_access(expression)
|
173
|
+
#return expression.to_sym if expression.start_with?(":")
|
174
|
+
subexpressions = expression.split(" AND ")
|
175
|
+
Proc.new do |person|
|
176
|
+
values = subexpressions.map {|exp| attribute_access(person, exp)}
|
177
|
+
values.compact.join(" ")
|
54
178
|
end
|
55
|
-
puts "Generated file #{file}"
|
56
179
|
end
|
57
180
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
181
|
+
# A way to use command line to specify part
|
182
|
+
# => i.e. person.details[first-name]
|
183
|
+
def attribute_access(person, expression)
|
184
|
+
parts = expression.split(".")
|
185
|
+
parts_to_value(person, parts).tap do |value|
|
186
|
+
unless value.is_a?(String) || !value
|
187
|
+
raise "Something is wrong with #{expression} to parts #{parts}. Expecting String, obtained: #{value.class}"
|
63
188
|
end
|
64
189
|
end
|
65
190
|
end
|
66
191
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
192
|
+
def parts_to_value(obj, parts)
|
193
|
+
parts.reduce(obj) do |object, part|
|
194
|
+
get_attr(object, part)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_attr(obj, part)
|
199
|
+
case
|
200
|
+
when !obj
|
201
|
+
nil
|
202
|
+
when part.is_a?(Symbol) || obj.respond_to?(part.to_sym)
|
203
|
+
obj.send(part.to_sym)
|
204
|
+
when part.start_with?(":")
|
205
|
+
get_attr(obj, part[1..-1])
|
206
|
+
when part.start_with?("details[")
|
207
|
+
if (obj.respond_to?(:details)) && details = obj.details
|
208
|
+
if match = part.match(/details\[(?<field>.*)\]/)
|
209
|
+
details[match[:field]]
|
210
|
+
else
|
211
|
+
raise "Review your -use-field expression. It should read: person.details[target-alt_id]"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
when part.start_with?("account")
|
215
|
+
obj.account if obj.respond_to?(:account)
|
216
|
+
when part.start_with?("person")
|
217
|
+
obj
|
218
|
+
else
|
219
|
+
raise "Review your expression. Cannot recognize '#{part}' as part of '#{obj.class}'"
|
73
220
|
end
|
74
221
|
end
|
75
222
|
|