betterplace-bi 0.7.0
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 +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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 119422d8d08ceabf4379d677d556c4dc2b6bcb32f0ae4f7b6304164bc7fabd59
|
4
|
+
data.tar.gz: 2cccf0803769aec1947716a60ea2d1dec45074785347b6a28058a92c99f7d3e9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 34c3d5aa9e9f363011bdf77fa53e5b4bfaaac9728a29f916d14e0b1b7769e0364c03e8c46ee1572e50316cfe09fe5e9ff18e9375fe7fb9de3078a3f20fee2373
|
7
|
+
data.tar.gz: 3b96febf3b0a6aa22ef134980e4a574603ea8d2020661af16abac9016a6354c84dc9d65a216664cfcc576e8fb790caf68feeb8e58a79e0af4cd5d31cde68430a
|
data/.gitignore
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
version: v1.0
|
2
|
+
name: Betterplace-BI pipeline
|
3
|
+
agent:
|
4
|
+
machine:
|
5
|
+
type: e1-standard-2
|
6
|
+
os_image: ubuntu2004
|
7
|
+
|
8
|
+
blocks:
|
9
|
+
- name: "Unit Tests"
|
10
|
+
task:
|
11
|
+
env_vars:
|
12
|
+
# Matches the configuration used in sem-service
|
13
|
+
- name: RAILS_ENV
|
14
|
+
value: test
|
15
|
+
jobs:
|
16
|
+
- name: RSpec
|
17
|
+
commands:
|
18
|
+
- checkout
|
19
|
+
|
20
|
+
# Setup ruby
|
21
|
+
- sem-version ruby $(awk '/^ruby/ { print $2 }' .tool-versions)
|
22
|
+
|
23
|
+
# Setup gems
|
24
|
+
- bundle config set path 'vendor/bundle'
|
25
|
+
- bundle install
|
26
|
+
- bundle exec rake spec
|
data/.tool-versions
ADDED
data/.utilsrc
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# vim: set ft=ruby:
|
2
|
+
|
3
|
+
search do
|
4
|
+
prune_dirs /\A(\.svn|\.git|CVS|tmp|tags|coverage|pkg)\z/
|
5
|
+
skip_files /(\A\.|\.sw[pon]\z|\.(log|fnm|jpg|jpeg|png|pdf|svg)\z|tags|~\z)/i
|
6
|
+
end
|
7
|
+
|
8
|
+
discover do
|
9
|
+
prune_dirs /\A(\.svn|\.git|CVS|tmp|tags|coverage|pkg)\z/
|
10
|
+
skip_files /(\A\.|\.sw[pon]\z|\.log\z|~\z)/
|
11
|
+
binary false
|
12
|
+
end
|
13
|
+
|
14
|
+
strip_spaces do
|
15
|
+
prune_dirs /\A(\..*|CVS|pkg)\z/
|
16
|
+
skip_files /(\A\.|\.sw[pon]\z|\.log\z|~\z)/
|
17
|
+
end
|
18
|
+
|
19
|
+
probe do
|
20
|
+
test_framework :rspec
|
21
|
+
#include_dirs 'features'
|
22
|
+
end
|
23
|
+
|
24
|
+
ssh_tunnel do
|
25
|
+
terminal_multiplexer :tmux
|
26
|
+
end
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Betterplace-BI
|
2
|
+
|
3
|
+
## Description
|
4
|
+
|
5
|
+
This library transfers BI data from an application to a BIME-server.
|
6
|
+
|
7
|
+
## Configuration
|
8
|
+
|
9
|
+
Via a `config/bi.yml` file in YAML like so:
|
10
|
+
|
11
|
+
```
|
12
|
+
---
|
13
|
+
production:
|
14
|
+
update: yes
|
15
|
+
auth:
|
16
|
+
username: bi
|
17
|
+
password: secret
|
18
|
+
clear: http://localhost:1234/api/v1/all/:id
|
19
|
+
endpoints:
|
20
|
+
BI::TestModelValue:
|
21
|
+
POST: http://localhost:1234/api/v1/tests
|
22
|
+
DELETE: http://localhost:1234/api/v1/test/:id
|
23
|
+
bime_dir:
|
24
|
+
<%= Pathname.pwd.join('../bime') %>
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
For every Rails model `TestModel` define a class named
|
30
|
+
`BI::TestModelValue` like this:
|
31
|
+
|
32
|
+
```
|
33
|
+
class BI::TestModelValue
|
34
|
+
include BI::SharedValue
|
35
|
+
|
36
|
+
def self.update_class
|
37
|
+
::TestModel
|
38
|
+
end
|
39
|
+
|
40
|
+
field type: 'string', db: 'gorm:"type:uuid;primary_key"'
|
41
|
+
def id
|
42
|
+
@object.id
|
43
|
+
end
|
44
|
+
|
45
|
+
field type: '*int64', db: 'gorm:"type:bigint"'
|
46
|
+
def number
|
47
|
+
10 ** 10
|
48
|
+
end
|
49
|
+
|
50
|
+
field type: '*time.Time', db: bp_timestamp # ISO timestamp string, e. g. "2018-11-05T15:40:14.362219+01:00"
|
51
|
+
def created_at
|
52
|
+
iso_timestamp(@object.created_at)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
Compute the values to be transfered by accessing @object and define GO types
|
58
|
+
and GORM struct tags for these values.
|
59
|
+
|
60
|
+
Also include `BI::Updater` in `TestModel` like so:
|
61
|
+
|
62
|
+
```
|
63
|
+
class TestModel < ApplicationRecord
|
64
|
+
include BI::Updater
|
65
|
+
|
66
|
+
has_one :dependent
|
67
|
+
|
68
|
+
def trigger_dependent_updates
|
69
|
+
dependent.schedule_bi_update_job(true)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
## License
|
75
|
+
|
76
|
+
This software is licensed under the Apache 2.0 license.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# vim: set filetype=ruby et sw=2 ts=2:
|
2
|
+
|
3
|
+
require 'gem_hadar'
|
4
|
+
|
5
|
+
GemHadar do
|
6
|
+
name 'betterplace-bi'
|
7
|
+
path_name 'bi'
|
8
|
+
path_module 'BI'
|
9
|
+
author 'Florian Frank'
|
10
|
+
email 'flori@ping.de'
|
11
|
+
homepage "https://github.com/betterplace/#{name}"
|
12
|
+
summary 'Business Intelligence library'
|
13
|
+
description 'This library contains functionality to send/store BI and AB-testing data'
|
14
|
+
test_dir 'spec'
|
15
|
+
ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', 'coverage', '.rvmrc',
|
16
|
+
'.AppleDouble', '.DS_Store', '.byebug_history', 'errors.lst', 'tags'
|
17
|
+
|
18
|
+
readme 'README.md'
|
19
|
+
title "#{name.camelize} -- BI library"
|
20
|
+
licenses << 'Apache-2.0'
|
21
|
+
|
22
|
+
dependency 'json'
|
23
|
+
dependency 'tins'
|
24
|
+
dependency 'complex_config'
|
25
|
+
dependency 'globalid'
|
26
|
+
dependency 'excon'
|
27
|
+
dependency 'rails'
|
28
|
+
dependency 'sinatra'
|
29
|
+
dependency 'betterlog'
|
30
|
+
dependency 'mize'
|
31
|
+
development_dependency 'rake'
|
32
|
+
development_dependency 'simplecov'
|
33
|
+
development_dependency 'rspec'
|
34
|
+
development_dependency 'byebug'
|
35
|
+
end
|
36
|
+
|
37
|
+
task :default => :spec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.7.0
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# stub: betterplace-bi 0.7.0 ruby lib
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "betterplace-bi".freeze
|
6
|
+
s.version = "0.7.0".freeze
|
7
|
+
|
8
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
9
|
+
s.require_paths = ["lib".freeze]
|
10
|
+
s.authors = ["Florian Frank".freeze]
|
11
|
+
s.date = "2024-04-08"
|
12
|
+
s.description = "This library contains functionality to send/store BI and AB-testing data".freeze
|
13
|
+
s.email = "flori@ping.de".freeze
|
14
|
+
s.extra_rdoc_files = ["README.md".freeze, "lib/betterplace-bi.rb".freeze, "lib/bi.rb".freeze, "lib/bi/ab_test_helper.rb".freeze, "lib/bi/api.rb".freeze, "lib/bi/commands.rb".freeze, "lib/bi/commands/base.rb".freeze, "lib/bi/commands/collector.rb".freeze, "lib/bi/commands/connection.rb".freeze, "lib/bi/commands/delete.rb".freeze, "lib/bi/commands/serializer.rb".freeze, "lib/bi/commands/update.rb".freeze, "lib/bi/commands_job.rb".freeze, "lib/bi/event.rb".freeze, "lib/bi/planning_value_parser.rb".freeze, "lib/bi/planning_value_validations.rb".freeze, "lib/bi/railtie.rb".freeze, "lib/bi/request_analyzer.rb".freeze, "lib/bi/session_id.rb".freeze, "lib/bi/shared_value.rb".freeze, "lib/bi/tracking.rb".freeze, "lib/bi/type_generator.rb".freeze, "lib/bi/update_error.rb".freeze, "lib/bi/updater.rb".freeze, "lib/bi/version.rb".freeze]
|
15
|
+
s.files = [".gitignore".freeze, ".semaphore/semaphore.yml".freeze, ".tool-versions".freeze, ".utilsrc".freeze, "Gemfile".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "betterplace-bi.gemspec".freeze, "lib/betterplace-bi.rb".freeze, "lib/bi.rb".freeze, "lib/bi/ab_test_helper.rb".freeze, "lib/bi/api.rb".freeze, "lib/bi/commands.rb".freeze, "lib/bi/commands/base.rb".freeze, "lib/bi/commands/collector.rb".freeze, "lib/bi/commands/connection.rb".freeze, "lib/bi/commands/delete.rb".freeze, "lib/bi/commands/serializer.rb".freeze, "lib/bi/commands/update.rb".freeze, "lib/bi/commands_job.rb".freeze, "lib/bi/event.rb".freeze, "lib/bi/planning_value_parser.rb".freeze, "lib/bi/planning_value_validations.rb".freeze, "lib/bi/railtie.rb".freeze, "lib/bi/request_analyzer.rb".freeze, "lib/bi/session_id.rb".freeze, "lib/bi/shared_value.rb".freeze, "lib/bi/tracking.rb".freeze, "lib/bi/type_generator.rb".freeze, "lib/bi/update_error.rb".freeze, "lib/bi/updater.rb".freeze, "lib/bi/version.rb".freeze, "lib/tasks/bime.rake".freeze, "spec/bi/ab_test_helper_spec.rb".freeze, "spec/bi/commands/collector_spec.rb".freeze, "spec/bi/commands/connection_spec.rb".freeze, "spec/bi/commands_job_spec.rb".freeze, "spec/bi/commands_spec.rb".freeze, "spec/bi/event_spec.rb".freeze, "spec/bi/planning_value_parser_spec.rb".freeze, "spec/bi/request_analyzer_spec.rb".freeze, "spec/bi/shared_value_spec.rb".freeze, "spec/bi/tracking_spec.rb".freeze, "spec/bi/type_generator_spec.rb".freeze, "spec/bi/updater_spec.rb".freeze, "spec/bime_dir/.keep".freeze, "spec/config/bi.yml".freeze, "spec/spec_helper.rb".freeze, "spec/support/models.rb".freeze]
|
16
|
+
s.homepage = "https://github.com/betterplace/betterplace-bi".freeze
|
17
|
+
s.licenses = ["Apache-2.0".freeze]
|
18
|
+
s.rdoc_options = ["--title".freeze, "Betterplace-bi -- BI library".freeze, "--main".freeze, "README.md".freeze]
|
19
|
+
s.rubygems_version = "3.4.21".freeze
|
20
|
+
s.summary = "Business Intelligence library".freeze
|
21
|
+
s.test_files = ["spec/bi/ab_test_helper_spec.rb".freeze, "spec/bi/commands/collector_spec.rb".freeze, "spec/bi/commands/connection_spec.rb".freeze, "spec/bi/commands_job_spec.rb".freeze, "spec/bi/commands_spec.rb".freeze, "spec/bi/event_spec.rb".freeze, "spec/bi/planning_value_parser_spec.rb".freeze, "spec/bi/request_analyzer_spec.rb".freeze, "spec/bi/shared_value_spec.rb".freeze, "spec/bi/tracking_spec.rb".freeze, "spec/bi/type_generator_spec.rb".freeze, "spec/bi/updater_spec.rb".freeze, "spec/spec_helper.rb".freeze, "spec/support/models.rb".freeze]
|
22
|
+
|
23
|
+
s.specification_version = 4
|
24
|
+
|
25
|
+
s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 1.11.0".freeze])
|
26
|
+
s.add_development_dependency(%q<rake>.freeze, [">= 0".freeze])
|
27
|
+
s.add_development_dependency(%q<simplecov>.freeze, [">= 0".freeze])
|
28
|
+
s.add_development_dependency(%q<rspec>.freeze, [">= 0".freeze])
|
29
|
+
s.add_development_dependency(%q<byebug>.freeze, [">= 0".freeze])
|
30
|
+
s.add_runtime_dependency(%q<json>.freeze, [">= 0".freeze])
|
31
|
+
s.add_runtime_dependency(%q<tins>.freeze, [">= 0".freeze])
|
32
|
+
s.add_runtime_dependency(%q<complex_config>.freeze, [">= 0".freeze])
|
33
|
+
s.add_runtime_dependency(%q<globalid>.freeze, [">= 0".freeze])
|
34
|
+
s.add_runtime_dependency(%q<excon>.freeze, [">= 0".freeze])
|
35
|
+
s.add_runtime_dependency(%q<rails>.freeze, [">= 0".freeze])
|
36
|
+
s.add_runtime_dependency(%q<sinatra>.freeze, [">= 0".freeze])
|
37
|
+
s.add_runtime_dependency(%q<betterlog>.freeze, [">= 0".freeze])
|
38
|
+
s.add_runtime_dependency(%q<mize>.freeze, [">= 0".freeze])
|
39
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'bi'
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module BI
|
2
|
+
module ABTestHelper
|
3
|
+
OVERRIDE_PARAM_NAME = "ab_test"
|
4
|
+
|
5
|
+
# Parameter +name+ is the name of the AB-test, the first element of
|
6
|
+
# +alternatives+ is the control. +request+ is a ActionDispatch::Request
|
7
|
+
# object.
|
8
|
+
def ab_test(name, *alternatives, request: self.request, &block)
|
9
|
+
alternatives.flatten!
|
10
|
+
alternatives.empty? and return
|
11
|
+
session_id, mode = nil, nil
|
12
|
+
chosen =
|
13
|
+
case
|
14
|
+
when override = ab_test_override?(name, *alternatives, request: request)
|
15
|
+
mode = :override
|
16
|
+
override
|
17
|
+
when ab_test_ignore_user?(request: request)
|
18
|
+
mode = :ignore
|
19
|
+
alternatives.first
|
20
|
+
when session = request.ask_and_send(:session)
|
21
|
+
session.loaded? or session.send(:load!)
|
22
|
+
session_id = BI::SessionID.session_id(session)
|
23
|
+
n = if session_id.empty?
|
24
|
+
mode = :random
|
25
|
+
rand(1 << 128)
|
26
|
+
else
|
27
|
+
mode = :session
|
28
|
+
Digest::MD5.hexdigest([ name, session_id ] * ?:).to_i(16)
|
29
|
+
end
|
30
|
+
alternatives[n % alternatives.size]
|
31
|
+
else
|
32
|
+
mode = :default
|
33
|
+
n = rand(1 << 128)
|
34
|
+
alternatives[n % alternatives.size]
|
35
|
+
end
|
36
|
+
Log.info(
|
37
|
+
"session.id=#{session_id} => #{chosen} was chosen for #{name} (#{mode})",
|
38
|
+
meta: { module: 'bi' }
|
39
|
+
)
|
40
|
+
block&.(chosen) || chosen
|
41
|
+
end
|
42
|
+
|
43
|
+
def ab_test_ignore_user?(request: self.request)
|
44
|
+
request and BI::RequestAnalyzer.new(request).ignore?
|
45
|
+
end
|
46
|
+
|
47
|
+
# It's possible to pick the control with the URL parameter
|
48
|
+
# 'ab_test=control' or ab_test=0, the first alternative by
|
49
|
+
# ab_test=alternative or ab_test=1, further alternatives by 2, 3,… It's
|
50
|
+
# also possible to pick one by using the version's name like ab_test=foo.
|
51
|
+
# If there is more than one AB-Test running, this parameter can be
|
52
|
+
# ab_test[name]=… to only switch the AB-Test
|
53
|
+
# named +name+ to the control or alternatives. This way it's possible to
|
54
|
+
# view all the possible permutations of controls and alternatives if you're
|
55
|
+
# crazy enough.
|
56
|
+
def ab_test_override?(name, *alternatives, request: self.request)
|
57
|
+
alternatives.flatten!
|
58
|
+
overriden = request.params[OVERRIDE_PARAM_NAME] rescue nil
|
59
|
+
case
|
60
|
+
when overriden.respond_to?(:to_str)
|
61
|
+
ab_test_override_alternative(overriden.to_str, *alternatives)
|
62
|
+
when overriden.respond_to?(:to_hash)
|
63
|
+
ab_test_override_alternative(overriden.to_hash[name], *alternatives)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def ab_test_override_alternative(override, *alternatives)
|
68
|
+
alternatives.flatten!
|
69
|
+
case override
|
70
|
+
when 'control'
|
71
|
+
alternatives.first
|
72
|
+
when 'alternative'
|
73
|
+
alternatives.second
|
74
|
+
when /\A\d+\z/
|
75
|
+
alternatives[$&.to_i] || alternatives.first
|
76
|
+
when *alternatives
|
77
|
+
override
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/bi/api.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
|
3
|
+
module BI
|
4
|
+
module API
|
5
|
+
|
6
|
+
module Helper
|
7
|
+
include BI::ABTestHelper
|
8
|
+
|
9
|
+
def self.included(modul)
|
10
|
+
modul.extend self
|
11
|
+
end
|
12
|
+
|
13
|
+
def hostname
|
14
|
+
@hostname ||= Socket.gethostname rescue 'n/a'
|
15
|
+
end
|
16
|
+
|
17
|
+
def status_code(symbol)
|
18
|
+
Rack::Utils::SYMBOL_TO_STATUS_CODE[symbol]
|
19
|
+
end
|
20
|
+
|
21
|
+
def rails_request
|
22
|
+
ActionDispatch::Request.new(env)
|
23
|
+
end
|
24
|
+
|
25
|
+
def rails_session
|
26
|
+
rails_request.session
|
27
|
+
end
|
28
|
+
|
29
|
+
def render_result(result, code: :ok, headers: {})
|
30
|
+
headers = headers.merge(
|
31
|
+
'Content-Type' => 'application/json',
|
32
|
+
)
|
33
|
+
code = status_code(code)
|
34
|
+
[ code, headers, result ]
|
35
|
+
end
|
36
|
+
|
37
|
+
def render_not_found
|
38
|
+
result = JSON(
|
39
|
+
hostname: hostname,
|
40
|
+
message: 'not found',
|
41
|
+
)
|
42
|
+
render_result(result, code: :not_found)
|
43
|
+
end
|
44
|
+
|
45
|
+
def render_exception(exception, code: :internal_server_error)
|
46
|
+
result = JSON(
|
47
|
+
hostname: hostname,
|
48
|
+
error: exception.class.name,
|
49
|
+
message: exception.message,
|
50
|
+
)
|
51
|
+
render_result(result, code: code)
|
52
|
+
end
|
53
|
+
|
54
|
+
def ab_test_result(channel, request)
|
55
|
+
config = cc.ab_tests[channel.to_s] or return
|
56
|
+
analyzer = BI::RequestAnalyzer.new(request)
|
57
|
+
current = ab_test(config.name, config.alternatives, request: rails_request)
|
58
|
+
version = "#{config.name}/#{current}"
|
59
|
+
{
|
60
|
+
channel: channel,
|
61
|
+
version: version,
|
62
|
+
session: BI::SessionID.session_id(request.session),
|
63
|
+
device_type: analyzer.device_type,
|
64
|
+
user_agent: analyzer.user_agent,
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class App < Sinatra::Base
|
70
|
+
include BI::API::Helper
|
71
|
+
|
72
|
+
before do
|
73
|
+
rr = rails_request
|
74
|
+
Log.info "Received #{rr.method} to #{rr.path}",
|
75
|
+
type: 'bi_request',
|
76
|
+
meta: {
|
77
|
+
module: 'bi',
|
78
|
+
url: rr.url,
|
79
|
+
method: rr.method,
|
80
|
+
params: rr.params,
|
81
|
+
referer: rr.referer,
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
get '/' do
|
86
|
+
render_result JSON(
|
87
|
+
endpoints: {
|
88
|
+
'/bi' => { methods: [ 'GET' ] },
|
89
|
+
'/bi/ab_test/:channel' => { methods: [ 'GET', 'POST' ] },
|
90
|
+
}
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
get '/ab_test/:channel' do
|
95
|
+
channel = params.fetch('channel')
|
96
|
+
result = ab_test_result(channel, rails_request) or return
|
97
|
+
render_result JSON(result)
|
98
|
+
end
|
99
|
+
|
100
|
+
post '/ab_test/:channel' do
|
101
|
+
channel = params.fetch('channel')
|
102
|
+
result = ab_test_result(channel, rails_request) or render_not_found
|
103
|
+
event_data = JSON(
|
104
|
+
request.body.tap(&:rewind).read
|
105
|
+
).symbolize_keys_recursive
|
106
|
+
event_data.update result
|
107
|
+
BI::Event.new.write(**event_data)
|
108
|
+
render_result JSON(result)
|
109
|
+
rescue => e
|
110
|
+
Log.error e, meta: { module: 'bi' }
|
111
|
+
render_exception(e, code: :unprocessable_entity)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module BI
|
2
|
+
module Commands
|
3
|
+
class Collector
|
4
|
+
def initialize
|
5
|
+
@first_object = nil
|
6
|
+
@commands = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def add(object, value_class)
|
10
|
+
@first_object ||= object
|
11
|
+
if object&.destroyed?
|
12
|
+
if url = value_class.url(method: 'DELETE', id: object.id)
|
13
|
+
@commands << BI::Commands::Delete.new(url: url)
|
14
|
+
end
|
15
|
+
else
|
16
|
+
value = value_class.new(object)
|
17
|
+
@commands << BI::Commands::Update.new(value: value)
|
18
|
+
end
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def schedule_job?(object)
|
23
|
+
@first_object == object
|
24
|
+
end
|
25
|
+
|
26
|
+
def schedule_job
|
27
|
+
BI::CommandsJob.new(*@commands).enqueue
|
28
|
+
self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module BI
|
2
|
+
module Commands
|
3
|
+
module Connection
|
4
|
+
module_function
|
5
|
+
|
6
|
+
def create_connection
|
7
|
+
Log.info("Connecting BI Server at %s now" % cc.bi.server)
|
8
|
+
Excon.new(
|
9
|
+
cc.bi.server,
|
10
|
+
user: cc.bi.auth.username,
|
11
|
+
password: cc.bi.auth.password,
|
12
|
+
persistent: true,
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def connect_to_server
|
17
|
+
connection = create_connection
|
18
|
+
yield connection
|
19
|
+
self
|
20
|
+
ensure
|
21
|
+
connection&.reset
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'bi/commands/base'
|
2
|
+
|
3
|
+
module BI
|
4
|
+
module Commands
|
5
|
+
class Delete < BI::Commands::Base
|
6
|
+
|
7
|
+
def url
|
8
|
+
data[:url]
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform_via(connection)
|
12
|
+
Log.info("Sending BI Delete to #{url}")
|
13
|
+
response = connection.delete(
|
14
|
+
path: url,
|
15
|
+
headers: {
|
16
|
+
'Content-Type' => 'application/json',
|
17
|
+
'Accept' => 'application/json',
|
18
|
+
}
|
19
|
+
)
|
20
|
+
case response.status
|
21
|
+
when 0...300
|
22
|
+
Log.info "Successfully sent delete to #{url}."
|
23
|
+
when 404
|
24
|
+
Log.warn "Duplicate deletion sent to #{url}: status=#{response.status}\n#{response.body}"
|
25
|
+
# Already deleted
|
26
|
+
else
|
27
|
+
Log.warn "Failed to sent delete to #{url}: status=#{response.status}\n#{response.body}"
|
28
|
+
raise BI::UpdateError, "status=#{response.status}: #{response.body}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module BI
|
2
|
+
module Commands
|
3
|
+
class Serializer < ActiveJob::Serializers::ObjectSerializer
|
4
|
+
# Checks if an argument should be serialized by this serializer.
|
5
|
+
def serialize?(argument)
|
6
|
+
argument.is_a? ::BI::Commands::Base
|
7
|
+
end
|
8
|
+
|
9
|
+
GLOBALID_KEY = "_bi_globalid"
|
10
|
+
|
11
|
+
# Converts an object to a simpler representative using supported object
|
12
|
+
# types. The recommended representative is a Hash with a specific key. Keys
|
13
|
+
# can be of basic types only. You should call `super` to add the custom
|
14
|
+
# serializer type to the hash.
|
15
|
+
def serialize(command)
|
16
|
+
hash = { class: command.class.name }
|
17
|
+
hash |= command.data.to_h.transform_values { |v|
|
18
|
+
if v.is_a?(GlobalID::Identification)
|
19
|
+
v = { GLOBALID_KEY => v.to_global_id.to_s }
|
20
|
+
end
|
21
|
+
v
|
22
|
+
}
|
23
|
+
hash.stringify_keys!
|
24
|
+
super(hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Converts serialized value into a proper object.
|
28
|
+
def deserialize(hash)
|
29
|
+
hash.delete("class").constantize.new(
|
30
|
+
hash.transform_values { |v|
|
31
|
+
if h = v.ask_and_send(:to_hash) and h.include?(GLOBALID_KEY)
|
32
|
+
v = GlobalID::Locator.locate h[GLOBALID_KEY]
|
33
|
+
end
|
34
|
+
v
|
35
|
+
}.symbolize_keys
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'bi/commands/base'
|
2
|
+
|
3
|
+
module BI
|
4
|
+
module Commands
|
5
|
+
class Update < BI::Commands::Base
|
6
|
+
def value
|
7
|
+
data[:value]
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform_via(connection)
|
11
|
+
Log.info("Sending BI Update value for %s" % value)
|
12
|
+
response = connection.post(
|
13
|
+
path: value.url,
|
14
|
+
headers: {
|
15
|
+
'Content-Type' => 'application/json',
|
16
|
+
'Accept' => 'application/json',
|
17
|
+
},
|
18
|
+
body: value.to_json,
|
19
|
+
)
|
20
|
+
if response.status < 300
|
21
|
+
Log.info "Successfully sent update for #{value}."
|
22
|
+
else
|
23
|
+
Log.warn "Failed to sent update for #{value}: status=#{response.status}\n#{response.body}"
|
24
|
+
raise BI::UpdateError, "status=#{response.status}: #{response.body}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/bi/commands.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_job/arguments'
|
2
|
+
|
3
|
+
module BI
|
4
|
+
class CommandsJob < ::ActiveJob::Base
|
5
|
+
include BI::Commands::Connection
|
6
|
+
|
7
|
+
queue_as :bime
|
8
|
+
|
9
|
+
retry_on Excon::Error::Socket, # handle temporary dns resolution problems
|
10
|
+
Excon::Error::Timeout # handle timeouts
|
11
|
+
|
12
|
+
retry_on(ActiveJob::DeserializationError, wait: :polynomially_longer, attempts: 4) {}
|
13
|
+
|
14
|
+
def perform(*commands)
|
15
|
+
commands.empty? and return
|
16
|
+
connect_to_server do |connection|
|
17
|
+
commands.each do |command|
|
18
|
+
command.perform_via(connection)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|