betterplace-bi 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.semaphore/semaphore.yml +26 -0
- data/.tool-versions +2 -0
- data/.utilsrc +26 -0
- data/Gemfile +5 -0
- data/README.md +76 -0
- data/Rakefile +37 -0
- data/VERSION +1 -0
- data/betterplace-bi.gemspec +39 -0
- data/lib/betterplace-bi.rb +1 -0
- data/lib/bi/ab_test_helper.rb +81 -0
- data/lib/bi/api.rb +115 -0
- data/lib/bi/commands/base.rb +11 -0
- data/lib/bi/commands/collector.rb +32 -0
- data/lib/bi/commands/connection.rb +25 -0
- data/lib/bi/commands/delete.rb +33 -0
- data/lib/bi/commands/serializer.rb +40 -0
- data/lib/bi/commands/update.rb +29 -0
- data/lib/bi/commands.rb +10 -0
- data/lib/bi/commands_job.rb +24 -0
- data/lib/bi/event.rb +52 -0
- data/lib/bi/planning_value_parser.rb +100 -0
- data/lib/bi/planning_value_validations.rb +15 -0
- data/lib/bi/railtie.rb +13 -0
- data/lib/bi/request_analyzer.rb +64 -0
- data/lib/bi/session_id.rb +11 -0
- data/lib/bi/shared_value.rb +102 -0
- data/lib/bi/tracking.rb +28 -0
- data/lib/bi/type_generator.rb +79 -0
- data/lib/bi/update_error.rb +4 -0
- data/lib/bi/updater.rb +61 -0
- data/lib/bi/version.rb +8 -0
- data/lib/bi.rb +27 -0
- data/lib/tasks/bime.rake +55 -0
- data/spec/bi/ab_test_helper_spec.rb +145 -0
- data/spec/bi/commands/collector_spec.rb +26 -0
- data/spec/bi/commands/connection_spec.rb +15 -0
- data/spec/bi/commands_job_spec.rb +24 -0
- data/spec/bi/commands_spec.rb +46 -0
- data/spec/bi/event_spec.rb +77 -0
- data/spec/bi/planning_value_parser_spec.rb +94 -0
- data/spec/bi/request_analyzer_spec.rb +75 -0
- data/spec/bi/shared_value_spec.rb +47 -0
- data/spec/bi/tracking_spec.rb +37 -0
- data/spec/bi/type_generator_spec.rb +44 -0
- data/spec/bi/updater_spec.rb +61 -0
- data/spec/bime_dir/.keep +0 -0
- data/spec/config/bi.yml +18 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/models.rb +106 -0
- metadata +331 -0
data/lib/bi/event.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module BI
|
2
|
+
class Event
|
3
|
+
def self.write(**args)
|
4
|
+
new.write(**args)
|
5
|
+
end
|
6
|
+
|
7
|
+
def event_class
|
8
|
+
cc.bi.event_class.constantize
|
9
|
+
end
|
10
|
+
|
11
|
+
# The parameter +channel+ is the channel that selects an ab test from the
|
12
|
+
# configuration.
|
13
|
+
# The paramter +name+ is the name of the event, e. g. show_form.
|
14
|
+
# The parameter session is either a session object or a session id string.
|
15
|
+
# In the former case the session object will be loaded if hasn't been
|
16
|
+
# loaded yet to generate the unique session id we need..
|
17
|
+
# The remaining parameters +**data+ are passed through to the created event
|
18
|
+
# as tracking parameters.
|
19
|
+
def write(
|
20
|
+
channel:, name:, version:, session:,
|
21
|
+
**data
|
22
|
+
)
|
23
|
+
event_class < BI::Tracking or
|
24
|
+
raise TypeError, 'event class has to mixin BI::Tracking'
|
25
|
+
|
26
|
+
if session.respond_to?(:loaded?)
|
27
|
+
session.loaded? or session.send(:load!)
|
28
|
+
session = BI::SessionID.session_id(session)
|
29
|
+
elsif session.respond_to?(:to_str)
|
30
|
+
session = session.to_str
|
31
|
+
else
|
32
|
+
session = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
created_event = event_class.create!(
|
36
|
+
tracking: {
|
37
|
+
channel: channel,
|
38
|
+
name: name,
|
39
|
+
version: version,
|
40
|
+
session: session,
|
41
|
+
**data.transform_keys { |k| k.to_s.sub(/\Atracking_/, '').to_sym },
|
42
|
+
}
|
43
|
+
)
|
44
|
+
Log.info "Stored a #{self.class} for #{name}",
|
45
|
+
meta: { module: 'bi', event: created_event.attributes }
|
46
|
+
created_event
|
47
|
+
rescue => e
|
48
|
+
Log.error(e, notify: true, meta: { module: 'bi' })
|
49
|
+
raise e
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module BI
|
5
|
+
class PlanningValueParser
|
6
|
+
class ParserError < StandardError
|
7
|
+
attr_accessor :row
|
8
|
+
|
9
|
+
attr_accessor :line
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.parse(csv_text, class_name:)
|
13
|
+
begin
|
14
|
+
model_class = Object.const_get(class_name)
|
15
|
+
rescue NameError
|
16
|
+
raise ParserError, "unknown class name #{class_name.inspect}"
|
17
|
+
end
|
18
|
+
csv_text = csv_text.gsub(/\r\n?/, "\n")
|
19
|
+
errors = {}
|
20
|
+
values = []
|
21
|
+
table = CSV.parse(csv_text, headers: true, row_sep: ?\n, col_sep: ?;)
|
22
|
+
table.each_with_index do |row, i|
|
23
|
+
values << Check.new(row, line: i + 2, class_name: class_name).value
|
24
|
+
rescue ParserError => e
|
25
|
+
errors[e.line] = e.message
|
26
|
+
end
|
27
|
+
new(values, errors: errors, model_class: model_class)
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(values, model_class:, errors: [])
|
31
|
+
@model_class = model_class
|
32
|
+
@values = values
|
33
|
+
@errors = errors
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :model_class
|
37
|
+
|
38
|
+
def import
|
39
|
+
model_class.where(data_version: data_versions).
|
40
|
+
destroy_all
|
41
|
+
each { |pv| pv.save! }
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
include Enumerable
|
46
|
+
def each(&block)
|
47
|
+
@values.each(&block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def data_versions
|
51
|
+
@values.each_with_object(Set[]) { |v, s| s << v.data_version }.to_a
|
52
|
+
end
|
53
|
+
|
54
|
+
def valid?
|
55
|
+
@errors.blank? && all?(&:valid?)
|
56
|
+
end
|
57
|
+
|
58
|
+
def errors
|
59
|
+
if @errors.present?
|
60
|
+
@errors
|
61
|
+
else
|
62
|
+
@values.each_with_index.each_with_object({}) do |(v, i), errors|
|
63
|
+
v.valid?
|
64
|
+
e = v.errors.messages.full? or next
|
65
|
+
errors[i + 2] = e.transform_values { |value| value.join(' & ') }.
|
66
|
+
map { |a| "%s: %s" % a }.join(' | ')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
VALID_HEADERS = [
|
72
|
+
"amount_in_cents",
|
73
|
+
"data_type",
|
74
|
+
"data_version",
|
75
|
+
"product_tracking",
|
76
|
+
"yearmonth",
|
77
|
+
].sort
|
78
|
+
|
79
|
+
class Check
|
80
|
+
def initialize(row, line:, class_name:)
|
81
|
+
parser_error = ParserError.new.tap { |pe|
|
82
|
+
pe.row = row
|
83
|
+
pe.line = line
|
84
|
+
}
|
85
|
+
if row.size != VALID_HEADERS.size
|
86
|
+
raise parser_error,
|
87
|
+
"#values has to match #headers = #{VALID_HEADERS.size}"
|
88
|
+
end
|
89
|
+
if row.headers.sort != VALID_HEADERS
|
90
|
+
raise parser_error, "headers need to be #{VALID_HEADERS.inspect}"
|
91
|
+
end
|
92
|
+
@value = Object.const_get(class_name).new(row.to_h)
|
93
|
+
end
|
94
|
+
|
95
|
+
attr_reader :value
|
96
|
+
|
97
|
+
delegate :valid?, to: :@value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module BI
|
2
|
+
module PlanningValueValidations
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
validates :product_tracking, presence: true
|
7
|
+
validates :amount_in_cents, presence: true,
|
8
|
+
numericality: { only_integer: true }
|
9
|
+
validates :yearmonth, presence: true,
|
10
|
+
format: { with: /\A[0-9]{4}-(1[0-2]|0[1-9])\z/ }
|
11
|
+
validates :data_type, presence: true
|
12
|
+
validates :data_version, presence: true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/bi/railtie.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module BI
|
2
|
+
if defined?(Rails::Railtie)
|
3
|
+
class Railtie < Rails::Railtie
|
4
|
+
initializer 'bi.configure_rails_initialization' do |app|
|
5
|
+
app.config.active_job.custom_serializers << BI::Commands::Serializer
|
6
|
+
end
|
7
|
+
|
8
|
+
rake_tasks do
|
9
|
+
load 'tasks/bime.rake'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module BI
|
2
|
+
class RequestAnalyzer
|
3
|
+
def initialize(request)
|
4
|
+
@request = request
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :request
|
8
|
+
|
9
|
+
def user_agent
|
10
|
+
request.user_agent
|
11
|
+
end
|
12
|
+
|
13
|
+
def ignore?
|
14
|
+
preview? || robot?
|
15
|
+
end
|
16
|
+
|
17
|
+
def preview?
|
18
|
+
request.headers['x-purpose'] == 'preview'
|
19
|
+
end
|
20
|
+
|
21
|
+
def robot?
|
22
|
+
robot_regexp.match?(user_agent)
|
23
|
+
end
|
24
|
+
|
25
|
+
def mobile?
|
26
|
+
mobile_regexp.match?(user_agent)
|
27
|
+
end
|
28
|
+
|
29
|
+
def device_type
|
30
|
+
case
|
31
|
+
when mobile?
|
32
|
+
:mobile
|
33
|
+
when robot?
|
34
|
+
:bot
|
35
|
+
else
|
36
|
+
:desktop
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def user_agent_configuration
|
43
|
+
cc.bi.requests.user_agents?
|
44
|
+
end
|
45
|
+
|
46
|
+
memoize function:
|
47
|
+
def robot_regexp
|
48
|
+
combine_patterns(user_agent_configuration&.robot?)
|
49
|
+
end
|
50
|
+
|
51
|
+
memoize function:
|
52
|
+
def mobile_regexp
|
53
|
+
combine_patterns(user_agent_configuration&.mobile?)
|
54
|
+
end
|
55
|
+
|
56
|
+
def combine_patterns(patterns)
|
57
|
+
if patterns
|
58
|
+
/\b(?:#{patterns.to_h.keys * ?|})\b|\A\W*\z/i
|
59
|
+
else
|
60
|
+
/\p{^any}/
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'active_support/time'
|
2
|
+
|
3
|
+
module BI
|
4
|
+
module SharedValue
|
5
|
+
extend Tins::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
annotate :field
|
9
|
+
|
10
|
+
include GlobalID::Identification
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def find(id)
|
15
|
+
new(update_class.find(id.ask_and_send_or_self(:id)))
|
16
|
+
end
|
17
|
+
|
18
|
+
implement def update_class() end, :subclass
|
19
|
+
|
20
|
+
GormTimestamp = Struct.new(:type, :gorm)
|
21
|
+
|
22
|
+
memoize function:
|
23
|
+
def bp_timestamp
|
24
|
+
Hash.new do |_, k|
|
25
|
+
raise KeyError, "illegal Attribute name #{k.inspect}"
|
26
|
+
end.merge(
|
27
|
+
Local: GormTimestamp.new('*time.Time', 'gorm:"type:timestamp;index"'),
|
28
|
+
YearLocal: GormTimestamp.new('*int', 'gorm:"type:int;index"'),
|
29
|
+
MonthLocal: GormTimestamp.new('*int', 'gorm:"type:int;index"'),
|
30
|
+
DayLocal: GormTimestamp.new('*int', 'gorm:"type:int;index"'),
|
31
|
+
HourLocal: GormTimestamp.new('*int', 'gorm:"type:int;index"'),
|
32
|
+
CWLocal: GormTimestamp.new('*int', 'gorm:"type:int;index"'),
|
33
|
+
DateLocal: GormTimestamp.new('*string', 'gorm:"type:varchar;index"'),
|
34
|
+
YearMonthLocal: GormTimestamp.new('*string', 'gorm:"type:varchar;index"'),
|
35
|
+
YearCWLocal: GormTimestamp.new('*string', 'gorm:"type:varchar;index"'),
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def url(method: 'POST', id: nil)
|
40
|
+
if ep = cc.bi.endpoints[name]
|
41
|
+
if u = ep[method]
|
42
|
+
id and return u = u.sub(':id', id)
|
43
|
+
u
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def clear
|
49
|
+
command = BI::Commands::Delete.new(
|
50
|
+
url: cc.bi.clear.sub(':id', update_class.name)
|
51
|
+
)
|
52
|
+
BI::Commands::Connection.connect_to_server do |connection|
|
53
|
+
command.perform_via(connection)
|
54
|
+
end
|
55
|
+
self
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize(object)
|
60
|
+
if object_uuid = object.ask_and_send(:to_str)
|
61
|
+
object = update_class.find(object_uuid)
|
62
|
+
end
|
63
|
+
@object = object
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def iso_timestamp(time)
|
69
|
+
if time
|
70
|
+
time.in_time_zone('Europe/Berlin').strftime('%FT%T.%6NZ') # with precision 6 sub seconds
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
public
|
75
|
+
|
76
|
+
def update_class
|
77
|
+
self.class.update_class
|
78
|
+
end
|
79
|
+
|
80
|
+
def url(**args)
|
81
|
+
self.class.url(**args)
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
"#<#{self.class} type=#{update_class} id=#{@object.id}>"
|
86
|
+
end
|
87
|
+
|
88
|
+
def attributes
|
89
|
+
field_annotations.each_with_object({}) do |(name, opts), result|
|
90
|
+
result[name] = __send__(name)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def as_json(*)
|
95
|
+
attributes
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_json(*a)
|
99
|
+
as_json.to_json(*a)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/bi/tracking.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module BI
|
2
|
+
module Tracking
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
serialize :tracking, coder: JSON
|
7
|
+
end
|
8
|
+
|
9
|
+
def tracking_hash
|
10
|
+
tracking.to_h.symbolize_keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to_missing?(name, include_private = false)
|
14
|
+
name =~ /\Atracking_(.+=|.*[^?!])\z/ or super
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(name, *args, &block)
|
18
|
+
case name
|
19
|
+
when /\Atracking_(.+)=\z/
|
20
|
+
tracking[$1] = args.first
|
21
|
+
when /\Atracking_(.*[^?!])\z/
|
22
|
+
tracking[$1]
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module BI
|
2
|
+
class TypeGenerator
|
3
|
+
def initialize(output_dir: cc.bi.bime_dir)
|
4
|
+
@output_dir = Pathname.new(output_dir)
|
5
|
+
@value_classes = cc.bi.endpoints.attribute_names.
|
6
|
+
map(&:to_s).map(&:constantize)
|
7
|
+
end
|
8
|
+
|
9
|
+
def timestamp_value(attribute_name, attribute_suffix, db_value)
|
10
|
+
%{ #{attribute_name + attribute_suffix.to_s} #{db_value.type} `json:"-" #{db_value.gorm}`}
|
11
|
+
end
|
12
|
+
|
13
|
+
def gofmt
|
14
|
+
command = `which gofmt`.chomp
|
15
|
+
return if command.empty?
|
16
|
+
command
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate
|
20
|
+
STDOUT.puts "Generating GO types in directory #{@output_dir}"
|
21
|
+
@value_classes.each do |vc|
|
22
|
+
c = vc.name.demodulize
|
23
|
+
output_filename = @output_dir.join(vc.update_class.name.underscore + ".go")
|
24
|
+
temp_io(name: 'go-type', content: -> src {
|
25
|
+
code = ''
|
26
|
+
STDOUT.print "Generating type code for #{c} …"
|
27
|
+
imports = Set[]
|
28
|
+
vc.field_annotations.each do |name, opts|
|
29
|
+
attribute_name = name.to_s.camelize
|
30
|
+
db = opts.fetch(:db)
|
31
|
+
case type = opts.fetch(:type)
|
32
|
+
when '*time.Time'
|
33
|
+
imports << 'time'
|
34
|
+
code << %{ #{attribute_name} #{type} `json:"#{name}" gorm:"-" timestamp:"split"`\n}
|
35
|
+
db.each do |attribute_suffix, db_value|
|
36
|
+
code << timestamp_value(attribute_name, attribute_suffix, db_value) << "\n"
|
37
|
+
end
|
38
|
+
next
|
39
|
+
when 'pq.StringArray'
|
40
|
+
imports << 'github.com/lib/pq'
|
41
|
+
end
|
42
|
+
code << %{ #{attribute_name} #{type} `json:"#{name}" #{db}`\n}
|
43
|
+
rescue KeyError, NoMethodError => e
|
44
|
+
raise "Error for #{name} #{e.class}: #{e}"
|
45
|
+
end
|
46
|
+
code << "}\n"
|
47
|
+
code = preamble(vc.update_class, imports: imports) + code
|
48
|
+
src.write code
|
49
|
+
STDOUT.puts "done."
|
50
|
+
}) do |src|
|
51
|
+
STDOUT.print "Outputting type code to #{output_filename} …"
|
52
|
+
File.open(output_filename, ?w) do |out|
|
53
|
+
if g = gofmt
|
54
|
+
out.puts `#{g} #{src.path}`
|
55
|
+
else
|
56
|
+
out.puts IO.read(src.path)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
STDOUT.puts "done."
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def preamble(type_name, imports:)
|
67
|
+
result = "package bime\n\n"
|
68
|
+
if i = imports.full?(:to_a)
|
69
|
+
result << <<~end
|
70
|
+
import (
|
71
|
+
#{i.map { |x| " #{x.dump}" }.join(?\n)}
|
72
|
+
)
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
result << "type #{type_name} struct {\n"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/bi/updater.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module BI
|
2
|
+
module Updater
|
3
|
+
extend Tins::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
before_save :schedule_bi_update_job_on_change, if: :changed?
|
7
|
+
|
8
|
+
before_destroy :schedule_bi_update_job_on_change
|
9
|
+
|
10
|
+
after_commit :schedule_bi_update_job
|
11
|
+
|
12
|
+
thread_local :change_detected, false
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
thread_local :bi_commands
|
17
|
+
end
|
18
|
+
|
19
|
+
def schedule_bi_update_job_on_change
|
20
|
+
self.change_detected = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def bi_commands
|
24
|
+
BI::Updater.bi_commands ||= BI::Commands::Collector.new
|
25
|
+
yield BI::Updater.bi_commands
|
26
|
+
if BI::Updater.bi_commands.schedule_job?(self)
|
27
|
+
BI::Updater.bi_commands.schedule_job
|
28
|
+
BI::Updater.bi_commands = nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def bi_update(really = true)
|
33
|
+
if cc.bi.update
|
34
|
+
if really
|
35
|
+
self.change_detected = false # Set to false as early as possible
|
36
|
+
bi_commands do |commands|
|
37
|
+
value_type = "BI::%sValue" % self.class.name
|
38
|
+
begin
|
39
|
+
value_class = value_type.constantize
|
40
|
+
rescue NameError => e
|
41
|
+
BI.error(e, notify: true, meta: { module: 'bi' })
|
42
|
+
else
|
43
|
+
BI::Updater.bi_commands.add(self, value_class)
|
44
|
+
end
|
45
|
+
ask_and_send(:trigger_dependent_updates)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
else
|
49
|
+
Log.info "Would send a BI Command for #{self.class}##{id} if not disabled"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def schedule_bi_update_job(default_change_detected = false)
|
54
|
+
self.change_detected ||= default_change_detected
|
55
|
+
bi_update(change_detected)
|
56
|
+
true
|
57
|
+
ensure
|
58
|
+
self.change_detected = false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/bi/version.rb
ADDED
data/lib/bi.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'complex_config/rude'
|
2
|
+
require 'tins/xt'
|
3
|
+
require 'globalid'
|
4
|
+
require 'excon'
|
5
|
+
require 'logger'
|
6
|
+
require 'betterlog'
|
7
|
+
|
8
|
+
module BI
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'bi/version'
|
12
|
+
require 'bi/railtie'
|
13
|
+
require 'bi/shared_value'
|
14
|
+
require 'bi/type_generator'
|
15
|
+
require 'bi/update_error'
|
16
|
+
require 'bi/updater'
|
17
|
+
require 'bi/commands'
|
18
|
+
require 'bi/commands_job' if defined?(ActiveJob::Base)
|
19
|
+
|
20
|
+
require 'bi/planning_value_validations'
|
21
|
+
require 'bi/planning_value_parser'
|
22
|
+
require 'bi/session_id'
|
23
|
+
require 'bi/event'
|
24
|
+
require 'bi/request_analyzer'
|
25
|
+
require 'bi/tracking'
|
26
|
+
require 'bi/ab_test_helper'
|
27
|
+
require 'bi/api'
|
data/lib/tasks/bime.rake
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
namespace :bime do
|
2
|
+
desc 'Generate golang type definitions'
|
3
|
+
task :types => :environment do
|
4
|
+
BI::TypeGenerator.new.generate
|
5
|
+
end
|
6
|
+
|
7
|
+
silence = proc { |&b| ActiveJob::Base.logger.silence(&b) }
|
8
|
+
|
9
|
+
desc 'Update all BI values'
|
10
|
+
task :update => :environment do |task, args|
|
11
|
+
cc.bi.update or raise "BI Update was disabled in cc.bi.update!"
|
12
|
+
|
13
|
+
args = args.to_a
|
14
|
+
|
15
|
+
if args.empty?
|
16
|
+
args.concat(cc.bi.endpoints.attribute_names.map { |n|
|
17
|
+
n.to_s.demodulize.sub(/Value\z/, '').underscore
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
models = args.map { |a| a.camelize.constantize }
|
22
|
+
|
23
|
+
silence.() {
|
24
|
+
models.each do |model|
|
25
|
+
model.find_each.with_infobar(label: model.name) do |o|
|
26
|
+
infobar << o.bi_update
|
27
|
+
end
|
28
|
+
infobar.finish
|
29
|
+
infobar.newline
|
30
|
+
end
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'Clear all BI values'
|
35
|
+
task :clear => :environment do |task, args|
|
36
|
+
cc.bi.update or raise "BI Update was disabled in cc.bi.update!"
|
37
|
+
|
38
|
+
args = args.to_a
|
39
|
+
if args.empty?
|
40
|
+
args.concat(cc.bi.endpoints.attribute_names.map { |n|
|
41
|
+
n.to_s.demodulize.sub(/Value\z/, '').underscore
|
42
|
+
})
|
43
|
+
end
|
44
|
+
|
45
|
+
args.each.with_infobar(label: 'BI Values') do |v|
|
46
|
+
infobar << "BI::#{(v + '_value').camelize}".constantize.clear
|
47
|
+
end
|
48
|
+
|
49
|
+
infobar.finish
|
50
|
+
infobar.newline
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'Reset all BI values'
|
54
|
+
task :reset => %i[ clear update ]
|
55
|
+
end
|