octocore 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +117 -0
  4. data/README.md +50 -0
  5. data/Rakefile +138 -0
  6. data/bin/fakestream +258 -0
  7. data/lib/octocore.rb +141 -0
  8. data/lib/octocore/baseline.rb +131 -0
  9. data/lib/octocore/callbacks.rb +78 -0
  10. data/lib/octocore/config.rb +37 -0
  11. data/lib/octocore/config/config.yml +1 -0
  12. data/lib/octocore/config/search/index/user.yml +42 -0
  13. data/lib/octocore/counter.rb +265 -0
  14. data/lib/octocore/counter/helpers.rb +168 -0
  15. data/lib/octocore/helpers.rb +6 -0
  16. data/lib/octocore/helpers/api_consumer_helper.rb +371 -0
  17. data/lib/octocore/helpers/api_helper.rb +53 -0
  18. data/lib/octocore/helpers/api_logger.rb +14 -0
  19. data/lib/octocore/helpers/client_helper.rb +104 -0
  20. data/lib/octocore/helpers/kong_helper.rb +156 -0
  21. data/lib/octocore/helpers/sinatra_helper.rb +22 -0
  22. data/lib/octocore/kafka_bridge.rb +60 -0
  23. data/lib/octocore/kldivergence.rb +14 -0
  24. data/lib/octocore/models.rb +260 -0
  25. data/lib/octocore/models/contactus.rb +17 -0
  26. data/lib/octocore/models/enterprise.rb +76 -0
  27. data/lib/octocore/models/enterprise/api_event.rb +14 -0
  28. data/lib/octocore/models/enterprise/api_hit.rb +20 -0
  29. data/lib/octocore/models/enterprise/api_track.rb +13 -0
  30. data/lib/octocore/models/enterprise/app_init.rb +13 -0
  31. data/lib/octocore/models/enterprise/app_login.rb +12 -0
  32. data/lib/octocore/models/enterprise/app_logout.rb +12 -0
  33. data/lib/octocore/models/enterprise/authorization.rb +61 -0
  34. data/lib/octocore/models/enterprise/category.rb +14 -0
  35. data/lib/octocore/models/enterprise/category_baseline.rb +19 -0
  36. data/lib/octocore/models/enterprise/category_hit.rb +26 -0
  37. data/lib/octocore/models/enterprise/category_trend.rb +19 -0
  38. data/lib/octocore/models/enterprise/conversions.rb +69 -0
  39. data/lib/octocore/models/enterprise/ctr.rb +54 -0
  40. data/lib/octocore/models/enterprise/dimension_choice.rb +21 -0
  41. data/lib/octocore/models/enterprise/engagement_time.rb +43 -0
  42. data/lib/octocore/models/enterprise/funnel_data.rb +20 -0
  43. data/lib/octocore/models/enterprise/funnels.rb +126 -0
  44. data/lib/octocore/models/enterprise/gcm.rb +21 -0
  45. data/lib/octocore/models/enterprise/newsfeed_hit.rb +52 -0
  46. data/lib/octocore/models/enterprise/notification_hit.rb +42 -0
  47. data/lib/octocore/models/enterprise/page.rb +15 -0
  48. data/lib/octocore/models/enterprise/page_view.rb +14 -0
  49. data/lib/octocore/models/enterprise/pageload_time.rb +43 -0
  50. data/lib/octocore/models/enterprise/product.rb +22 -0
  51. data/lib/octocore/models/enterprise/product_baseline.rb +20 -0
  52. data/lib/octocore/models/enterprise/product_hit.rb +26 -0
  53. data/lib/octocore/models/enterprise/product_page_view.rb +13 -0
  54. data/lib/octocore/models/enterprise/product_trend.rb +18 -0
  55. data/lib/octocore/models/enterprise/push_key.rb +15 -0
  56. data/lib/octocore/models/enterprise/rules.rb +45 -0
  57. data/lib/octocore/models/enterprise/segment.rb +65 -0
  58. data/lib/octocore/models/enterprise/segment_data.rb +22 -0
  59. data/lib/octocore/models/enterprise/tag.rb +14 -0
  60. data/lib/octocore/models/enterprise/tag_baseline.rb +19 -0
  61. data/lib/octocore/models/enterprise/tag_hit.rb +26 -0
  62. data/lib/octocore/models/enterprise/tag_trend.rb +19 -0
  63. data/lib/octocore/models/enterprise/template.rb +18 -0
  64. data/lib/octocore/models/plans.rb +17 -0
  65. data/lib/octocore/models/subscribe.rb +12 -0
  66. data/lib/octocore/models/user.rb +22 -0
  67. data/lib/octocore/models/user/push_token.rb +15 -0
  68. data/lib/octocore/models/user/user_browser_details.rb +16 -0
  69. data/lib/octocore/models/user/user_location_history.rb +15 -0
  70. data/lib/octocore/models/user/user_persona.rb +101 -0
  71. data/lib/octocore/models/user/user_phone_details.rb +17 -0
  72. data/lib/octocore/models/user/user_profile.rb +20 -0
  73. data/lib/octocore/models/user/user_timeline.rb +111 -0
  74. data/lib/octocore/record.rb +20 -0
  75. data/lib/octocore/schedeuleable.rb +20 -0
  76. data/lib/octocore/scheduler.rb +59 -0
  77. data/lib/octocore/search.rb +5 -0
  78. data/lib/octocore/search/client.rb +33 -0
  79. data/lib/octocore/search/indexer.rb +0 -0
  80. data/lib/octocore/search/searchable.rb +18 -0
  81. data/lib/octocore/search/setup.rb +71 -0
  82. data/lib/octocore/segment.rb +285 -0
  83. data/lib/octocore/stats.rb +33 -0
  84. data/lib/octocore/trendable.rb +88 -0
  85. data/lib/octocore/trends.rb +158 -0
  86. data/lib/octocore/utils.rb +90 -0
  87. data/lib/octocore/version.rb +4 -0
  88. data/spec/lib/stats_spec.rb +20 -0
  89. data/spec/spec_helper.rb +103 -0
  90. metadata +436 -0
@@ -0,0 +1,17 @@
1
+ require 'cequel'
2
+
3
+ module Octo
4
+ class UserPhoneDetails
5
+ include Cequel::Record
6
+
7
+ belongs_to :user, class_name: 'Octo::User'
8
+
9
+ key :deviceid, :text
10
+ column :manufacturer, :text
11
+ column :model, :text
12
+ column :os, :text
13
+
14
+ timestamps
15
+ end
16
+ end
17
+
@@ -0,0 +1,20 @@
1
+ require 'cequel'
2
+
3
+ module Octo
4
+ class UserProfileDetails
5
+ include Cequel::Record
6
+
7
+ belongs_to :user, class_name: 'Octo::User'
8
+
9
+ key :email, :text
10
+ column :username, :text
11
+ column :dob, :text
12
+ column :gender, :text
13
+ column :alternate_email, :text
14
+ column :mobile, :text
15
+ column :extras, :text
16
+
17
+ timestamps
18
+ end
19
+ end
20
+
@@ -0,0 +1,111 @@
1
+ require 'cequel'
2
+ require 'ostruct'
3
+
4
+ module Octo
5
+ class UserTimeline
6
+ include Cequel::Record
7
+
8
+ BROWSE_PRODUCT = 0
9
+ BROWSE_PAGE = 1
10
+ SEARCH = 2
11
+ SHARE = 3
12
+ ADD_TO_CART = 4
13
+ CHECKOUT = 5
14
+ APP_OPEN = 6
15
+ APP_CLOSE = 7
16
+ PAGE_RELOAD = 8
17
+
18
+ LOC_HOME = 11
19
+ LOC_OFFICE = 12
20
+ LOC_TRANSIT = 13
21
+ LOC_VACATION = 14
22
+ LOC_OOH = 15
23
+ LOC_OTHERS = 16
24
+
25
+ belongs_to :user, class_name: 'Octo::User'
26
+
27
+ key :ts, :timestamp
28
+
29
+ column :type, :int
30
+ column :title, :text
31
+ column :location_type, :int
32
+ column :insight, :text
33
+ column :details, :text
34
+
35
+ timestamps
36
+
37
+ def self.fakedata(user, n = rand(7..20))
38
+ Array.new(3*n) do |i|
39
+ i+1
40
+ end.shuffle.sample(n).sort.reverse.collect do |i|
41
+ args = {
42
+ user: user,
43
+ ts: i.minutes.ago,
44
+ type: rand(0..8),
45
+ title: 'Product Name',
46
+ location_type: rand(11..16),
47
+ insight: 'some valueable insight',
48
+ details: 'other details here'
49
+ }
50
+ self.new(args).save!
51
+ end
52
+ end
53
+
54
+ def location_text(location_type)
55
+ case location_type
56
+ when LOC_HOME
57
+ 'Home'
58
+ when LOC_OFFICE
59
+ 'Office'
60
+ when LOC_TRANSIT
61
+ 'In Transit'
62
+ when LOC_VACATION
63
+ 'While Vacation'
64
+ when LOC_OOH
65
+ 'Out of Home City'
66
+ when LOC_OTHERS
67
+ 'Other Location'
68
+ end
69
+ end
70
+
71
+ def type_text(activity_type)
72
+ case activity_type
73
+ when BROWSE_PRODUCT
74
+ 'Browsed for Product'
75
+ when BROWSE_PAGE
76
+ 'Browsed for Page'
77
+ when SEARCH
78
+ 'Searched'
79
+ when SHARE
80
+ 'Shared'
81
+ when ADD_TO_CART
82
+ 'Added to Cart'
83
+ when CHECKOUT
84
+ 'Performed Checkout'
85
+ when APP_OPEN
86
+ 'Opened App'
87
+ when APP_CLOSE
88
+ 'Closed App'
89
+ when PAGE_RELOAD
90
+ 'Reloaded Page'
91
+ end
92
+ end
93
+
94
+ def human_readable
95
+ args = {
96
+ user: self.user,
97
+ ts: self.ts,
98
+ type: type_text(self.type),
99
+ type_raw: self.type,
100
+ title: self.title,
101
+ location: location_text(self.location_type),
102
+ location_raw: self.location_type,
103
+ insight: self.insight,
104
+ details: self.details
105
+ }
106
+ OpenStruct.new(args)
107
+ end
108
+
109
+ end
110
+ end
111
+
@@ -0,0 +1,20 @@
1
+ module Octo
2
+ module Record
3
+
4
+ def unique_id
5
+ candidates = self.key_attributes
6
+ if candidates.length == 1
7
+ # This is most likely going to be the enterpriseid of some sort
8
+ candidates.first[1].to_s
9
+ elsif candidates.length == 2
10
+ if candidates.has_key?(:enterprise_id)
11
+ candidates.delete(:enterprise_id)
12
+ candidates.first[1].to_s
13
+ end
14
+ else
15
+ raise NotImplementedError, 'See Octo::Record#unique_id'
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ require 'octocore/counter'
2
+
3
+ module Octo
4
+ module Scheduleable
5
+
6
+ def perform(*args)
7
+ type = args[0].to_sym
8
+
9
+ if Octo::Counter.constants.include?type
10
+ if type == :TYPE_MINUTE and self.respond_to?(:aggregate!)
11
+ aggregate!
12
+ else
13
+ method_name = type_counters_method_names type
14
+ send(method_name.to_sym, Time.now.floor)
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,59 @@
1
+ require 'rake'
2
+ require 'resque'
3
+ require 'resque/tasks'
4
+ require 'resque/scheduler/tasks'
5
+
6
+ # Make sure dynamic scheduling is turned ON
7
+ Resque::Scheduler.dynamic = true
8
+
9
+ module Octo
10
+ module Scheduler
11
+
12
+ # Setup the schedules for counters.
13
+ def schedule_counters
14
+ counter_classes = [
15
+ Octo::ProductHit,
16
+ Octo::CategoryHit,
17
+ Octo::TagHit,
18
+ Octo::ApiHit,
19
+ Octo::NewsfeedHit
20
+ ]
21
+ counter_classes.each do |clazz|
22
+ clazz.send(:get_typecounters).each do |counter|
23
+ name = [clazz, counter].join('::')
24
+ config = {
25
+ class: clazz.to_s,
26
+ args: [counter],
27
+ cron: '* * * * *',
28
+ persist: true,
29
+ queue: 'high'
30
+ }
31
+ Resque.set_schedule name, config
32
+ end
33
+ end
34
+
35
+ # Schedules the processing of baselines
36
+ def schedule_baseline
37
+ baseline_classes = [
38
+ Octo::ProductBaseline,
39
+ Octo::CategoryBaseline,
40
+ Octo::TagBaseline
41
+ ]
42
+ baseline_classes.each do |clazz|
43
+ clazz.send(:get_typecounters).each do |counter|
44
+ name = [clazz, counter].join('::')
45
+ config = {
46
+ class: clazz.to_s,
47
+ args: [counter],
48
+ cron: '* * * * *',
49
+ persists: true,
50
+ queue: 'baseline_processing'
51
+ }
52
+ Resque.set_schedule name, config
53
+ end
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ require 'octocore/search/client'
2
+ require 'octocore/search/setup'
3
+ require 'octocore/search/indexer'
4
+ require 'octocore/search/searchable'
5
+
@@ -0,0 +1,33 @@
1
+ require 'multi_json'
2
+ require 'faraday'
3
+ require 'elasticsearch/api'
4
+
5
+ module Octo
6
+
7
+ # Search wrapper around ElasticSearch
8
+ module Search
9
+
10
+ class Client
11
+
12
+ include Elasticsearch::API
13
+
14
+ CONNECTION = ::Faraday::Connection.new(url: Octo.get_config(:search)[:server])
15
+
16
+ # Low level method for performing a request to Elastic Search cluster
17
+ # @param [String] method The method ex: get, put, post, etc..
18
+ # @param [String] path The path of the request
19
+ # @param [Hash] params The params of the request
20
+ # @param [String] body The body of the request
21
+ def perform_request(method, path, params, body)
22
+ Octo.logger.debug "--> #{method.upcase} #{path} #{params} #{body}"
23
+
24
+ CONNECTION.run_request \
25
+ method.downcase.to_sym,
26
+ path,
27
+ ( body ? MultiJson.dump(body): nil ),
28
+ {'Content-Type' => 'application/json'}
29
+ end
30
+ end
31
+ end
32
+ end
33
+
File without changes
@@ -0,0 +1,18 @@
1
+ module Octo
2
+ module Searchable
3
+
4
+ # Gets the search client
5
+ def searchclient
6
+ unless @searchClient
7
+ @searchClient = Octo::Search::Client.new
8
+ end
9
+ @searchClient
10
+ end
11
+
12
+ # Defines the indice with which this would be indexed
13
+ def indexable_with(indice_name, type)
14
+
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,71 @@
1
+ module Octo
2
+
3
+ # Setup module for ElasticSearch
4
+ module Setup
5
+
6
+ # Creates the necessary indices
7
+ class Create
8
+
9
+ def self.perform
10
+ sclient = Octo::Search::Client.new
11
+ sconfig = Octo.get_config(:search)
12
+
13
+ # Set the cluster disk space thresholds first. That's necessary
14
+ # because the defaults are too less for development machines. So,
15
+ # in order to keep it moving, we set a lower threshold in development.
16
+ # Refer
17
+ # https://www.elastic.co/guide/en/elasticsearch/reference/current/disk-allocator.html
18
+ if sconfig.has_key?(:disk_threshold_low) and sconfig.has_key?(:disk_threshold_high)
19
+ cluster_settings = {
20
+ body: {
21
+ persistent: {
22
+ 'cluster.routing.allocation.disk.threshold_enabled' => true,
23
+ 'cluster.routing.allocation.disk.watermark.low' => sconfig[:disk_threshold_low],
24
+ 'cluster.routing.allocation.disk.watermark.high' => sconfig[:disk_threshold_high],
25
+ 'cluster.info.update.interval' => '60s'
26
+ }
27
+ }
28
+ }
29
+ sclient.cluster.put_settings cluster_settings
30
+ end
31
+
32
+ # Check if any indices specified exists. If not exists, create them
33
+ sconfig[:index].keys.each do |index_name|
34
+ args = { index: index_name }
35
+ if sclient.indices.exists?(args)
36
+ Octo.logger.info "Search Index: #{ index_name } exists."
37
+ else
38
+ Octo.logger.warn "Search Index: #{ index_name } DOES NOT EXIST."
39
+ Octo.logger.info "Creating Index: #{ index_name }"
40
+ create_args = {
41
+ index: index_name,
42
+ body: sconfig[:index][index_name]
43
+ }
44
+ sclient.indices.create create_args
45
+ end
46
+ end
47
+
48
+ # Also check if there are any indices present that should not be
49
+ # present
50
+ _indices = JSON.parse(sclient.cluster.state)['metadata']['indices'].
51
+ keys.map(&:to_sym)
52
+ extra_indices = _indices - sconfig[:index].keys
53
+ Octo.logger.warn "Found extra indices: #{ extra_indices }"
54
+ end
55
+ end
56
+
57
+ # Updates the indices.
58
+ # The major differene between this and the Create is that while create
59
+ # just checks for the existance by name, and passes if the name is found
60
+ # This actually overwrites all the mappings, properties, warmers etc
61
+ # So, this should be used only when we need to explicitly "UPDATE" the
62
+ # index.
63
+ class Update
64
+
65
+ end
66
+
67
+
68
+ end
69
+
70
+
71
+ end
@@ -0,0 +1,285 @@
1
+ require 'set'
2
+ require 'active_support/concern'
3
+ require 'octocore/helpers/api_consumer_helper'
4
+
5
+ module Octo
6
+
7
+ # The Segmentation module
8
+ module Segmentation
9
+
10
+ extend ActiveSupport::Concern
11
+
12
+ module Helpers
13
+
14
+ class << self
15
+
16
+ # Helper method for returning operators as a hash to be used in UX
17
+ # @param [Fixnum] dimension The dimension for which the operators
18
+ # are to be fetched
19
+ # @return [Array<Hash{Symbol => String }>] The hash containing key :text
20
+ # as the text to display, and another key :id as the id to be used
21
+ # as reference while communicating
22
+ def operators_as_choice(dimension = nil)
23
+ mapping_as_choice operator_text
24
+ end
25
+
26
+ # Helper method for returning dimensions as choice to be used in the UX
27
+ # @return [Array<Hash{Symbol => String }>] The hash containing key :text
28
+ # as the text to display, and another key :id as the id to be used
29
+ # as reference while communicating
30
+ def dimensions_as_choice
31
+ mapping_as_choice dimension_text
32
+ end
33
+
34
+ # Returns logic operatos as a choice for operating between dimensions
35
+ def logic_operators_as_choice
36
+ mapping_as_choice logic_text
37
+ end
38
+
39
+ # Helper method to return valid choices for a given dimension. It tries
40
+ # to find the values from db first. In case, there is nothing, it
41
+ # shows some default values, so the dashboard does not look totally
42
+ # blank.
43
+ # @param [Fixnum] dimension The dimension ID for which choices to be
44
+ # found
45
+ # @param [String] enterprise_id The enterprise ID for which the choices
46
+ # to be found
47
+ def choices_for_dimensions(dimension, enterprise_id)
48
+ args = {
49
+ enterprise_id: enterprise_id,
50
+ dimension: dimension
51
+ }
52
+ res = Octo::DimensionChoice.where(args)
53
+ choices = Array.new
54
+ if res.count > 0
55
+ choices = res.collect do |r|
56
+ r.column
57
+ end
58
+ elsif dimension_choice.has_key?(dimension)
59
+ func = dimension_choice[dimension]
60
+ choices = self.send(func, enterprise_id)
61
+ end
62
+ mapping_as_choice Hash[Array.new(choices.count) { |i| i }.zip(choices)]
63
+ end
64
+
65
+
66
+ private
67
+
68
+ # Returns a hash containing keys as Operators and the values as string
69
+ # text corresponding to them.
70
+ def operator_text
71
+ {
72
+ Octo::Segmentation::Operators::EQUAL => '= Equals',
73
+ Octo::Segmentation::Operators::NOT_EQUAL => '!= Not Equals',
74
+ Octo::Segmentation::Operators::IN => 'Within range'
75
+ }
76
+ end
77
+
78
+ # Return a hash containing keys as dimensions and the values as methods
79
+ # which should be called to populate the values
80
+ def dimension_choice
81
+ {
82
+ Octo::Segmentation::Dimensions::CITY => :city_choices,
83
+ Octo::Segmentation::Dimensions::STATE => :state_choices,
84
+ Octo::Segmentation::Dimensions::COUNTRY => :country_choices,
85
+ Octo::Segmentation::Dimensions::OS => :os_choices,
86
+ Octo::Segmentation::Dimensions::MANUFACTURER => :manufacturer_choices,
87
+ Octo::Segmentation::Dimensions::BROWSER => :browser_choices,
88
+ Octo::Segmentation::Dimensions::MODEL => :model_choices,
89
+ Octo::Segmentation::Dimensions::ENGAGEMENT => :engagement_choices
90
+ }
91
+ end
92
+
93
+ # Returns a hash containing the dimension value and the string text
94
+ def dimension_text
95
+ {
96
+ Octo::Segmentation::Dimensions::CITY => 'City',
97
+ Octo::Segmentation::Dimensions::STATE => 'State',
98
+ Octo::Segmentation::Dimensions::COUNTRY => 'Country',
99
+ Octo::Segmentation::Dimensions::OS => 'OS',
100
+ Octo::Segmentation::Dimensions::MANUFACTURER => 'Manufacturer',
101
+ Octo::Segmentation::Dimensions::BROWSER => 'Browser',
102
+ Octo::Segmentation::Dimensions::MODEL => 'Model',
103
+ Octo::Segmentation::Dimensions::ENGAGEMENT => 'Engagement',
104
+ Octo::Segmentation::Dimensions::LAST_ACTIVE => 'Last Active On',
105
+ Octo::Segmentation::Dimensions::CREATED_ON => 'Created On'
106
+ }
107
+ end
108
+
109
+ # Defines which operators can be used for which dimensions
110
+ def dimension_operator
111
+ ops = Set.new([Octo::Segmentation::Operators::EQUAL,
112
+ Octo::Segmentation::Operators::NOT_EQUAL,
113
+ Octo::Segmentation::Operators::IN])
114
+ Hash.new { |h,k| h[k] = ops }
115
+ end
116
+
117
+ def logic_text
118
+ {
119
+ Octo::Segmentation::Operators::AND => 'AND',
120
+ Octo::Segmentation::Operators::OR => 'OR',
121
+ Octo::Segmentation::Operators::NOT => 'NOT',
122
+ Octo::Segmentation::Operators::XOR => 'XOR',
123
+ }
124
+ end
125
+
126
+ # Generates the city choices for the enterprise
127
+ # @param [String] enterprise_id The enterpriseID for which city choices
128
+ # to be found
129
+ # @return [Array<String>] Array of string values
130
+ def city_choices(enterprise_id=nil)
131
+ ['New Delhi', 'Mumbai', 'Bengaluru', 'San Francisco', 'Seattle']
132
+ end
133
+
134
+ def state_choices(enterprise_id=nil)
135
+ ['Delhi', 'Maharashtra', 'Karnataka', 'California']
136
+ end
137
+
138
+ def country_choices(enterprise_id=nil)
139
+ ['India', 'United States of America (USA)']
140
+ end
141
+
142
+ def os_choices(enterprise_id=nil)
143
+ ['Windows', 'OS X', 'iOS', 'android']
144
+ end
145
+
146
+ def manufacturer_choices(enterprise_id=nil)
147
+ ['Apple', 'Dell', 'HP', 'Samsung', 'Micromax']
148
+ end
149
+
150
+ def browser_choices(enterprise_id=nil)
151
+ ['Firefox', 'Chrome', 'Safari']
152
+ end
153
+
154
+ def model_choices(enterprise_id=nil)
155
+ ['iPhone 6', 'iPhone 6s', 'iPhone 5', 'Samsung S6']
156
+ end
157
+
158
+ def engagement_choices(enterprise_id=nil)
159
+ [Octo::UserPersona::HIGH_ENGAGED,
160
+ Octo::UserPersona::MEDIUM_ENGAGED,
161
+ Octo::UserPersona::LOW_ENGAGED,
162
+ Octo::UserPersona::DEAD].collect { |x| Octo::UserPersona.engaged_text(x) }
163
+ end
164
+
165
+
166
+ # Converts a hash mapping into choices ready for UX
167
+ # @return [Array<Hash{Symbol => String }>] The hash containing key :text
168
+ # as the text to display, and another key :id as the id to be used
169
+ # as reference while communicating
170
+ def mapping_as_choice(map)
171
+ map.inject([]) do | choices, pair |
172
+ key, val = pair
173
+ choices << { text: val, id: key }
174
+ end
175
+ end
176
+
177
+ end
178
+ end
179
+
180
+ module SegmentType
181
+
182
+ USER = 0
183
+ EVENT = 1
184
+ end
185
+
186
+ # The Operators modules. Defines Operators and necessary methods around
187
+ module Operators
188
+
189
+ EQUAL = 0
190
+ NOT_EQUAL = 1
191
+ GTE = 2
192
+ GT = 3
193
+ LTE = 4
194
+ LT = 5
195
+ IN = 6
196
+ AND = 7
197
+ OR = 8
198
+ NOT = 9
199
+ XOR = 10
200
+
201
+ class << self
202
+
203
+ # Returns if the given operator is valid or not
204
+ def valid?(operator)
205
+ Set.new([EQUAL, NOT_EQUAL, GTE, GT, LTE, LT, IN]).include?(operator.to_i)
206
+ end
207
+
208
+ end
209
+ end
210
+
211
+ # The Dimensions module. Defines the dimensions possible and its abstraction
212
+ module Dimensions
213
+
214
+ # Geographical Dimensions seems most obvious
215
+ CITY = 0
216
+ STATE = 1
217
+ COUNTRY = 2
218
+
219
+ # Followed by User's device details
220
+ OS = 3
221
+ MANUFACTURER = 4
222
+ BROWSER = 5
223
+ MODEL = 6
224
+
225
+ # Followed by User's engagement patterns
226
+ ENGAGEMENT = 7
227
+
228
+ # What about their last active, created dates
229
+ LAST_ACTIVE = 8
230
+ CREATED_ON = 9
231
+
232
+ # Usage Pattern
233
+ DAYTIME_USAGE = 10
234
+
235
+
236
+ end
237
+
238
+ # Extend
239
+ module ClassMethods
240
+
241
+ # Returns a boolean specifying if the segment is a valid choice or not
242
+ # @param [String] segment The string to be evaluated
243
+ # @return [Boolean] If the provided string exists in valid choices
244
+ def is_valid_segment segment
245
+ all_segment_choices.include?segment
246
+ end
247
+
248
+ # Returns all possible segment choices. Segment choices are the first
249
+ # data point that is picked on top of which segment would be made. It
250
+ # could be one of the supported octo events (eg: app.init etc) or users
251
+ def all_segment_choices
252
+ valid_events << :users
253
+ end
254
+
255
+ private
256
+
257
+ # Get all the valid events
258
+ # @return [Set<Symbol>] Valid events globally
259
+ def valid_events
260
+ Set.new(Octo.get_config(:allowed_events))
261
+ end
262
+
263
+
264
+
265
+ # Merges choices and returns the set with opts taken care of
266
+ # @param [Array] c The initial choices
267
+ # @param [Hash] opts Optional hash specifying anything to be exluded or
268
+ # included
269
+ # @option opts [Array<String>] :include Strings to be included in choices
270
+ # @option opts [Array<String>] :exclude Strings to be excluded in choices
271
+ # @return [Array<String>] Merged choices
272
+ def merge_choices(c, opts={})
273
+ Set.new(c).merge(
274
+ Set.new(opts.fetch(:include, []))
275
+ ).subtract(
276
+ Set.new(opts.fetch(:exclude, []))
277
+ )
278
+ end
279
+
280
+
281
+ end
282
+
283
+ end
284
+ end
285
+