analytics-ruby 2.2.5 → 2.4.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 +4 -4
- data/bin/analytics +22 -7
- data/lib/segment/analytics/client.rb +48 -272
- data/lib/segment/analytics/field_parser.rb +192 -0
- data/lib/segment/analytics/logging.rb +34 -7
- data/lib/segment/analytics/message_batch.rb +16 -2
- data/lib/segment/analytics/test_queue.rb +56 -0
- data/lib/segment/analytics/{request.rb → transport.rb} +17 -13
- data/lib/segment/analytics/utils.rb +2 -6
- data/lib/segment/analytics/version.rb +1 -1
- data/lib/segment/analytics/worker.rb +15 -7
- data/lib/segment/analytics.rb +4 -2
- metadata +11 -44
- data/Gemfile +0 -2
- data/History.md +0 -222
- data/Makefile +0 -17
- data/README.md +0 -84
- data/RELEASING.md +0 -9
- data/Rakefile +0 -23
- data/analytics-ruby.gemspec +0 -33
- data/codecov.yml +0 -2
- data/lib/segment/analytics/message.rb +0 -26
- data/spec/helpers/runscope_client.rb +0 -38
- data/spec/segment/analytics/backoff_policy_spec.rb +0 -92
- data/spec/segment/analytics/client_spec.rb +0 -328
- data/spec/segment/analytics/e2e_spec.rb +0 -48
- data/spec/segment/analytics/message_batch_spec.rb +0 -49
- data/spec/segment/analytics/message_spec.rb +0 -35
- data/spec/segment/analytics/request_spec.rb +0 -244
- data/spec/segment/analytics/response_spec.rb +0 -30
- data/spec/segment/analytics/worker_spec.rb +0 -110
- data/spec/segment/analytics_spec.rb +0 -120
- data/spec/spec_helper.rb +0 -128
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 51f3f02afed05c9c8461b454e0a3067962a75b8efde0930db0098eebf4a62829
         | 
| 4 | 
            +
              data.tar.gz: e7d486d6c6186535e9a005c4d950e6e29d2dd9937f9b6da625191d786f76846a
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: c2ce09d450de65620eb99f9fae86aa5ce57709d53aa4a93c73c2d3db0b173afad532383a50fe4dcb953df72171b731a263ff7308aed79b646a25bc202c555e4d
         | 
| 7 | 
            +
              data.tar.gz: b3625484ebca6117ae79fc42f03a595dff861b8c5ca02a6ef12ea9e933229c1479dad40b4db1d85e61ddf585c7c5a072d68e9cd21ced6b2482db338f45073707
         | 
    
        data/bin/analytics
    CHANGED
    
    | @@ -29,6 +29,7 @@ command :send do |c| | |
| 29 29 | 
             
              c.option '--userId=<userId>', String, 'the user id to send the event as'
         | 
| 30 30 | 
             
              c.option '--anonymousId=<anonymousId>', String, 'the anonymous user id to send the event as'
         | 
| 31 31 | 
             
              c.option '--context=<context>', 'additional context for the event (JSON-encoded)'
         | 
| 32 | 
            +
              c.option '--integrations=<integrations>', 'additional integrations for the event (JSON-encoded)'
         | 
| 32 33 |  | 
| 33 34 | 
             
              c.option '--event=<event>', String, 'the event name to send with the event'
         | 
| 34 35 | 
             
              c.option '--properties=<properties>', 'the event properties to send (JSON-encoded)'
         | 
| @@ -38,6 +39,7 @@ command :send do |c| | |
| 38 39 | 
             
              c.option '--traits=<traits>', 'the identify/group traits to send (JSON-encoded)'
         | 
| 39 40 |  | 
| 40 41 | 
             
              c.option '--groupId=<groupId>', String, 'the group id'
         | 
| 42 | 
            +
              c.option '--previousId=<previousId>', String, 'the previous id'
         | 
| 41 43 |  | 
| 42 44 | 
             
              c.action do |args, options|
         | 
| 43 45 | 
             
                Analytics = Segment::Analytics.new({
         | 
| @@ -52,7 +54,8 @@ command :send do |c| | |
| 52 54 | 
             
                    event: options.event,
         | 
| 53 55 | 
             
                    anonymous_id: options.anonymousId,
         | 
| 54 56 | 
             
                    properties: json_hash(options.properties),
         | 
| 55 | 
            -
                    context: json_hash(options.context)
         | 
| 57 | 
            +
                    context: json_hash(options.context),
         | 
| 58 | 
            +
                    integrations: json_hash(options.integrations)
         | 
| 56 59 | 
             
                  })
         | 
| 57 60 | 
             
                when "page"
         | 
| 58 61 | 
             
                  Analytics.page({
         | 
| @@ -60,22 +63,25 @@ command :send do |c| | |
| 60 63 | 
             
                    anonymous_id: options.anonymousId,
         | 
| 61 64 | 
             
                    name: options.name,
         | 
| 62 65 | 
             
                    properties: json_hash(options.properties),
         | 
| 63 | 
            -
                    context: json_hash(options.context)
         | 
| 66 | 
            +
                    context: json_hash(options.context),
         | 
| 67 | 
            +
                    integrations: json_hash(options.integrations)
         | 
| 64 68 | 
             
                  })
         | 
| 65 69 | 
             
                when "screen"
         | 
| 66 70 | 
             
                  Analytics.screen({
         | 
| 67 71 | 
             
                    user_id: options.userId,
         | 
| 68 72 | 
             
                    anonymous_id: options.anonymousId,
         | 
| 69 | 
            -
                    name:  | 
| 70 | 
            -
                     | 
| 71 | 
            -
                     | 
| 73 | 
            +
                    name: options.name,
         | 
| 74 | 
            +
                    properties: json_hash(options.properties),
         | 
| 75 | 
            +
                    context: json_hash(options.context),
         | 
| 76 | 
            +
                    integrations: json_hash(options.integrations)
         | 
| 72 77 | 
             
                  })
         | 
| 73 78 | 
             
                when "identify"
         | 
| 74 79 | 
             
                  Analytics.identify({
         | 
| 75 80 | 
             
                    user_id: options.userId,
         | 
| 76 81 | 
             
                    anonymous_id: options.anonymousId,
         | 
| 77 82 | 
             
                    traits: json_hash(options.traits),
         | 
| 78 | 
            -
                    context: json_hash(options.context)
         | 
| 83 | 
            +
                    context: json_hash(options.context),
         | 
| 84 | 
            +
                    integrations: json_hash(options.integrations)
         | 
| 79 85 | 
             
                  })
         | 
| 80 86 | 
             
                when "group"
         | 
| 81 87 | 
             
                  Analytics.group({
         | 
| @@ -83,7 +89,16 @@ command :send do |c| | |
| 83 89 | 
             
                    anonymous_id: options.anonymousId,
         | 
| 84 90 | 
             
                    group_id: options.groupId,
         | 
| 85 91 | 
             
                    traits: json_hash(options.traits),
         | 
| 86 | 
            -
                    context: json_hash(options.context)
         | 
| 92 | 
            +
                    context: json_hash(options.context),
         | 
| 93 | 
            +
                    integrations: json_hash(options.integrations)
         | 
| 94 | 
            +
                  })
         | 
| 95 | 
            +
                when "alias"
         | 
| 96 | 
            +
                  Analytics.alias({
         | 
| 97 | 
            +
                    previous_id: options.previousId,
         | 
| 98 | 
            +
                    user_id: options.userId,
         | 
| 99 | 
            +
                    anonymous_id: options.anonymousId,
         | 
| 100 | 
            +
                    context: json_hash(options.context),
         | 
| 101 | 
            +
                    integrations: json_hash(options.integrations)
         | 
| 87 102 | 
             
                  })
         | 
| 88 103 | 
             
                else
         | 
| 89 104 | 
             
                  raise "Invalid Message Type #{options.type}"
         | 
| @@ -21,11 +21,12 @@ module Segment | |
| 21 21 | 
             
                    symbolize_keys!(opts)
         | 
| 22 22 |  | 
| 23 23 | 
             
                    @queue = Queue.new
         | 
| 24 | 
            +
                    @test = opts[:test]
         | 
| 24 25 | 
             
                    @write_key = opts[:write_key]
         | 
| 25 26 | 
             
                    @max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE
         | 
| 26 | 
            -
                    @options = opts
         | 
| 27 27 | 
             
                    @worker_mutex = Mutex.new
         | 
| 28 | 
            -
                    @worker = Worker.new(@queue, @write_key,  | 
| 28 | 
            +
                    @worker = Worker.new(@queue, @write_key, opts)
         | 
| 29 | 
            +
                    @worker_thread = nil
         | 
| 29 30 |  | 
| 30 31 | 
             
                    check_write_key!
         | 
| 31 32 |  | 
| @@ -43,58 +44,32 @@ module Segment | |
| 43 44 | 
             
                    end
         | 
| 44 45 | 
             
                  end
         | 
| 45 46 |  | 
| 47 | 
            +
                  # @!macro common_attrs
         | 
| 48 | 
            +
                  #   @option attrs [String] :anonymous_id ID for a user when you don't know
         | 
| 49 | 
            +
                  #     who they are yet. (optional but you must provide either an
         | 
| 50 | 
            +
                  #     `anonymous_id` or `user_id`)
         | 
| 51 | 
            +
                  #   @option attrs [Hash] :context ({})
         | 
| 52 | 
            +
                  #   @option attrs [Hash] :integrations What integrations this event
         | 
| 53 | 
            +
                  #     goes to (optional)
         | 
| 54 | 
            +
                  #   @option attrs [String] :message_id ID that uniquely
         | 
| 55 | 
            +
                  #     identifies a message across the API. (optional)
         | 
| 56 | 
            +
                  #   @option attrs [Time] :timestamp When the event occurred (optional)
         | 
| 57 | 
            +
                  #   @option attrs [String] :user_id The ID for this user in your database
         | 
| 58 | 
            +
                  #     (optional but you must provide either an `anonymous_id` or `user_id`)
         | 
| 59 | 
            +
                  #   @option attrs [Hash] :options Options such as user traits (optional)
         | 
| 60 | 
            +
             | 
| 46 61 | 
             
                  # Tracks an event
         | 
| 47 62 | 
             
                  #
         | 
| 48 63 | 
             
                  # @see https://segment.com/docs/sources/server/ruby/#track
         | 
| 49 64 | 
             
                  #
         | 
| 50 65 | 
             
                  # @param [Hash] attrs
         | 
| 51 | 
            -
                  # | 
| 52 | 
            -
                  #   who they are yet. (optional but you must provide either an
         | 
| 53 | 
            -
                  #   `anonymous_id` or `user_id`)
         | 
| 54 | 
            -
                  # @option attrs [Hash] :context ({})
         | 
| 66 | 
            +
                  #
         | 
| 55 67 | 
             
                  # @option attrs [String] :event Event name
         | 
| 56 | 
            -
                  # @option attrs [Hash] :integrations What integrations this event
         | 
| 57 | 
            -
                  #   goes to (optional)
         | 
| 58 | 
            -
                  # @option attrs [Hash] :options Options such as user traits (optional)
         | 
| 59 68 | 
             
                  # @option attrs [Hash] :properties Event properties (optional)
         | 
| 60 | 
            -
                  # @ | 
| 61 | 
            -
                  # @option attrs [String] :user_id The ID for this user in your database
         | 
| 62 | 
            -
                  #   (optional but you must provide either an `anonymous_id` or `user_id`)
         | 
| 63 | 
            -
                  # @option attrs [String] :message_id ID that uniquely
         | 
| 64 | 
            -
                  #   identifies a message across the API. (optional)
         | 
| 69 | 
            +
                  # @macro common_attrs
         | 
| 65 70 | 
             
                  def track(attrs)
         | 
| 66 71 | 
             
                    symbolize_keys! attrs
         | 
| 67 | 
            -
                     | 
| 68 | 
            -
             | 
| 69 | 
            -
                    event = attrs[:event]
         | 
| 70 | 
            -
                    properties = attrs[:properties] || {}
         | 
| 71 | 
            -
                    timestamp = attrs[:timestamp] || Time.new
         | 
| 72 | 
            -
                    context = attrs[:context] || {}
         | 
| 73 | 
            -
                    message_id = attrs[:message_id].to_s if attrs[:message_id]
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                    check_timestamp! timestamp
         | 
| 76 | 
            -
             | 
| 77 | 
            -
                    if event.nil? || event.empty?
         | 
| 78 | 
            -
                      raise ArgumentError, 'Must supply event as a non-empty string'
         | 
| 79 | 
            -
                    end
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                    raise ArgumentError, 'Properties must be a Hash' unless properties.is_a? Hash
         | 
| 82 | 
            -
                    isoify_dates! properties
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                    add_context context
         | 
| 85 | 
            -
             | 
| 86 | 
            -
                    enqueue({
         | 
| 87 | 
            -
                      :event => event,
         | 
| 88 | 
            -
                      :userId => attrs[:user_id],
         | 
| 89 | 
            -
                      :anonymousId => attrs[:anonymous_id],
         | 
| 90 | 
            -
                      :context => context,
         | 
| 91 | 
            -
                      :options => attrs[:options],
         | 
| 92 | 
            -
                      :integrations => attrs[:integrations],
         | 
| 93 | 
            -
                      :properties => properties,
         | 
| 94 | 
            -
                      :messageId => message_id,
         | 
| 95 | 
            -
                      :timestamp => datetime_in_iso8601(timestamp),
         | 
| 96 | 
            -
                      :type => 'track'
         | 
| 97 | 
            -
                    })
         | 
| 72 | 
            +
                    enqueue(FieldParser.parse_for_track(attrs))
         | 
| 98 73 | 
             
                  end
         | 
| 99 74 |  | 
| 100 75 | 
             
                  # Identifies a user
         | 
| @@ -102,46 +77,12 @@ module Segment | |
| 102 77 | 
             
                  # @see https://segment.com/docs/sources/server/ruby/#identify
         | 
| 103 78 | 
             
                  #
         | 
| 104 79 | 
             
                  # @param [Hash] attrs
         | 
| 105 | 
            -
                  # | 
| 106 | 
            -
                  #   who they are yet. (optional but you must provide either an
         | 
| 107 | 
            -
                  #   `anonymous_id` or `user_id`)
         | 
| 108 | 
            -
                  # @option attrs [Hash] :context ({})
         | 
| 109 | 
            -
                  # @option attrs [Hash] :integrations What integrations this event
         | 
| 110 | 
            -
                  #   goes to (optional)
         | 
| 111 | 
            -
                  # @option attrs [Hash] :options Options such as user traits (optional)
         | 
| 112 | 
            -
                  # @option attrs [Time] :timestamp When the event occurred (optional)
         | 
| 80 | 
            +
                  #
         | 
| 113 81 | 
             
                  # @option attrs [Hash] :traits User traits (optional)
         | 
| 114 | 
            -
                  # @ | 
| 115 | 
            -
                  #   (optional but you must provide either an `anonymous_id` or `user_id`)
         | 
| 116 | 
            -
                  # @option attrs [String] :message_id ID that uniquely identifies a
         | 
| 117 | 
            -
                  #   message across the API. (optional)
         | 
| 82 | 
            +
                  # @macro common_attrs
         | 
| 118 83 | 
             
                  def identify(attrs)
         | 
| 119 84 | 
             
                    symbolize_keys! attrs
         | 
| 120 | 
            -
                     | 
| 121 | 
            -
             | 
| 122 | 
            -
                    traits = attrs[:traits] || {}
         | 
| 123 | 
            -
                    timestamp = attrs[:timestamp] || Time.new
         | 
| 124 | 
            -
                    context = attrs[:context] || {}
         | 
| 125 | 
            -
                    message_id = attrs[:message_id].to_s if attrs[:message_id]
         | 
| 126 | 
            -
             | 
| 127 | 
            -
                    check_timestamp! timestamp
         | 
| 128 | 
            -
             | 
| 129 | 
            -
                    raise ArgumentError, 'Must supply traits as a hash' unless traits.is_a? Hash
         | 
| 130 | 
            -
                    isoify_dates! traits
         | 
| 131 | 
            -
             | 
| 132 | 
            -
                    add_context context
         | 
| 133 | 
            -
             | 
| 134 | 
            -
                    enqueue({
         | 
| 135 | 
            -
                      :userId => attrs[:user_id],
         | 
| 136 | 
            -
                      :anonymousId => attrs[:anonymous_id],
         | 
| 137 | 
            -
                      :integrations => attrs[:integrations],
         | 
| 138 | 
            -
                      :context => context,
         | 
| 139 | 
            -
                      :traits => traits,
         | 
| 140 | 
            -
                      :options => attrs[:options],
         | 
| 141 | 
            -
                      :messageId => message_id,
         | 
| 142 | 
            -
                      :timestamp => datetime_in_iso8601(timestamp),
         | 
| 143 | 
            -
                      :type => 'identify'
         | 
| 144 | 
            -
                    })
         | 
| 85 | 
            +
                    enqueue(FieldParser.parse_for_identify(attrs))
         | 
| 145 86 | 
             
                  end
         | 
| 146 87 |  | 
| 147 88 | 
             
                  # Aliases a user from one id to another
         | 
| @@ -149,39 +90,12 @@ module Segment | |
| 149 90 | 
             
                  # @see https://segment.com/docs/sources/server/ruby/#alias
         | 
| 150 91 | 
             
                  #
         | 
| 151 92 | 
             
                  # @param [Hash] attrs
         | 
| 152 | 
            -
                  # | 
| 153 | 
            -
                  # @option attrs [Hash] :integrations What integrations this must be
         | 
| 154 | 
            -
                  #   sent to (optional)
         | 
| 155 | 
            -
                  # @option attrs [Hash] :options Options such as user traits (optional)
         | 
| 93 | 
            +
                  #
         | 
| 156 94 | 
             
                  # @option attrs [String] :previous_id The ID to alias from
         | 
| 157 | 
            -
                  # @ | 
| 158 | 
            -
                  # @option attrs [String] :user_id The ID to alias to
         | 
| 159 | 
            -
                  # @option attrs [String] :message_id ID that uniquely identifies a
         | 
| 160 | 
            -
                  #   message across the API. (optional)
         | 
| 95 | 
            +
                  # @macro common_attrs
         | 
| 161 96 | 
             
                  def alias(attrs)
         | 
| 162 97 | 
             
                    symbolize_keys! attrs
         | 
| 163 | 
            -
             | 
| 164 | 
            -
                    from = attrs[:previous_id]
         | 
| 165 | 
            -
                    to = attrs[:user_id]
         | 
| 166 | 
            -
                    timestamp = attrs[:timestamp] || Time.new
         | 
| 167 | 
            -
                    context = attrs[:context] || {}
         | 
| 168 | 
            -
                    message_id = attrs[:message_id].to_s if attrs[:message_id]
         | 
| 169 | 
            -
             | 
| 170 | 
            -
                    check_presence! from, 'previous_id'
         | 
| 171 | 
            -
                    check_presence! to, 'user_id'
         | 
| 172 | 
            -
                    check_timestamp! timestamp
         | 
| 173 | 
            -
                    add_context context
         | 
| 174 | 
            -
             | 
| 175 | 
            -
                    enqueue({
         | 
| 176 | 
            -
                      :previousId => from,
         | 
| 177 | 
            -
                      :userId => to,
         | 
| 178 | 
            -
                      :integrations => attrs[:integrations],
         | 
| 179 | 
            -
                      :context => context,
         | 
| 180 | 
            -
                      :options => attrs[:options],
         | 
| 181 | 
            -
                      :messageId => message_id,
         | 
| 182 | 
            -
                      :timestamp => datetime_in_iso8601(timestamp),
         | 
| 183 | 
            -
                      :type => 'alias'
         | 
| 184 | 
            -
                    })
         | 
| 98 | 
            +
                    enqueue(FieldParser.parse_for_alias(attrs))
         | 
| 185 99 | 
             
                  end
         | 
| 186 100 |  | 
| 187 101 | 
             
                  # Associates a user identity with a group.
         | 
| @@ -189,48 +103,13 @@ module Segment | |
| 189 103 | 
             
                  # @see https://segment.com/docs/sources/server/ruby/#group
         | 
| 190 104 | 
             
                  #
         | 
| 191 105 | 
             
                  # @param [Hash] attrs
         | 
| 192 | 
            -
                  # | 
| 193 | 
            -
                  #   who they are yet. (optional but you must provide either an
         | 
| 194 | 
            -
                  #   `anonymous_id` or `user_id`)
         | 
| 195 | 
            -
                  # @option attrs [Hash] :context ({})
         | 
| 106 | 
            +
                  #
         | 
| 196 107 | 
             
                  # @option attrs [String] :group_id The ID of the group
         | 
| 197 | 
            -
                  # @option attrs [Hash] : | 
| 198 | 
            -
                  # | 
| 199 | 
            -
                  # @option attrs [Hash] :options Options such as user traits (optional)
         | 
| 200 | 
            -
                  # @option attrs [Time] :timestamp When the event occurred (optional)
         | 
| 201 | 
            -
                  # @option attrs [String] :user_id The ID for the user that is part of
         | 
| 202 | 
            -
                  #   the group
         | 
| 203 | 
            -
                  # @option attrs [String] :message_id ID that uniquely identifies a
         | 
| 204 | 
            -
                  #   message across the API. (optional)
         | 
| 108 | 
            +
                  # @option attrs [Hash] :traits User traits (optional)
         | 
| 109 | 
            +
                  # @macro common_attrs
         | 
| 205 110 | 
             
                  def group(attrs)
         | 
| 206 111 | 
             
                    symbolize_keys! attrs
         | 
| 207 | 
            -
                     | 
| 208 | 
            -
             | 
| 209 | 
            -
                    group_id = attrs[:group_id]
         | 
| 210 | 
            -
                    user_id = attrs[:user_id]
         | 
| 211 | 
            -
                    traits = attrs[:traits] || {}
         | 
| 212 | 
            -
                    timestamp = attrs[:timestamp] || Time.new
         | 
| 213 | 
            -
                    context = attrs[:context] || {}
         | 
| 214 | 
            -
                    message_id = attrs[:message_id].to_s if attrs[:message_id]
         | 
| 215 | 
            -
             | 
| 216 | 
            -
                    raise ArgumentError, '.traits must be a hash' unless traits.is_a? Hash
         | 
| 217 | 
            -
                    isoify_dates! traits
         | 
| 218 | 
            -
             | 
| 219 | 
            -
                    check_presence! group_id, 'group_id'
         | 
| 220 | 
            -
                    check_timestamp! timestamp
         | 
| 221 | 
            -
                    add_context context
         | 
| 222 | 
            -
             | 
| 223 | 
            -
                    enqueue({
         | 
| 224 | 
            -
                      :groupId => group_id,
         | 
| 225 | 
            -
                      :userId => user_id,
         | 
| 226 | 
            -
                      :traits => traits,
         | 
| 227 | 
            -
                      :integrations => attrs[:integrations],
         | 
| 228 | 
            -
                      :options => attrs[:options],
         | 
| 229 | 
            -
                      :context => context,
         | 
| 230 | 
            -
                      :messageId => message_id,
         | 
| 231 | 
            -
                      :timestamp => datetime_in_iso8601(timestamp),
         | 
| 232 | 
            -
                      :type => 'group'
         | 
| 233 | 
            -
                    })
         | 
| 112 | 
            +
                    enqueue(FieldParser.parse_for_group(attrs))
         | 
| 234 113 | 
             
                  end
         | 
| 235 114 |  | 
| 236 115 | 
             
                  # Records a page view
         | 
| @@ -238,97 +117,26 @@ module Segment | |
| 238 117 | 
             
                  # @see https://segment.com/docs/sources/server/ruby/#page
         | 
| 239 118 | 
             
                  #
         | 
| 240 119 | 
             
                  # @param [Hash] attrs
         | 
| 241 | 
            -
                  # | 
| 242 | 
            -
                  #   who they are yet. (optional but you must provide either an
         | 
| 243 | 
            -
                  #   `anonymous_id` or `user_id`)
         | 
| 244 | 
            -
                  # @option attrs [String] :category The page category (optional)
         | 
| 245 | 
            -
                  # @option attrs [Hash] :context ({})
         | 
| 246 | 
            -
                  # @option attrs [Hash] :integrations What integrations this event
         | 
| 247 | 
            -
                  #   goes to (optional)
         | 
| 120 | 
            +
                  #
         | 
| 248 121 | 
             
                  # @option attrs [String] :name Name of the page
         | 
| 249 | 
            -
                  # @option attrs [Hash] :options Options such as user traits (optional)
         | 
| 250 122 | 
             
                  # @option attrs [Hash] :properties Page properties (optional)
         | 
| 251 | 
            -
                  # @ | 
| 252 | 
            -
                  # @option attrs [String] :user_id The ID of the user viewing the page
         | 
| 253 | 
            -
                  # @option attrs [String] :message_id ID that uniquely identifies a
         | 
| 254 | 
            -
                  #   message across the API. (optional)
         | 
| 123 | 
            +
                  # @macro common_attrs
         | 
| 255 124 | 
             
                  def page(attrs)
         | 
| 256 125 | 
             
                    symbolize_keys! attrs
         | 
| 257 | 
            -
                     | 
| 258 | 
            -
             | 
| 259 | 
            -
                    name = attrs[:name].to_s
         | 
| 260 | 
            -
                    properties = attrs[:properties] || {}
         | 
| 261 | 
            -
                    timestamp = attrs[:timestamp] || Time.new
         | 
| 262 | 
            -
                    context = attrs[:context] || {}
         | 
| 263 | 
            -
                    message_id = attrs[:message_id].to_s if attrs[:message_id]
         | 
| 264 | 
            -
             | 
| 265 | 
            -
                    raise ArgumentError, '.properties must be a hash' unless properties.is_a? Hash
         | 
| 266 | 
            -
                    isoify_dates! properties
         | 
| 267 | 
            -
             | 
| 268 | 
            -
                    check_timestamp! timestamp
         | 
| 269 | 
            -
                    add_context context
         | 
| 270 | 
            -
             | 
| 271 | 
            -
                    enqueue({
         | 
| 272 | 
            -
                      :userId => attrs[:user_id],
         | 
| 273 | 
            -
                      :anonymousId => attrs[:anonymous_id],
         | 
| 274 | 
            -
                      :name => name,
         | 
| 275 | 
            -
                      :category => attrs[:category],
         | 
| 276 | 
            -
                      :properties => properties,
         | 
| 277 | 
            -
                      :integrations => attrs[:integrations],
         | 
| 278 | 
            -
                      :options => attrs[:options],
         | 
| 279 | 
            -
                      :context => context,
         | 
| 280 | 
            -
                      :messageId => message_id,
         | 
| 281 | 
            -
                      :timestamp => datetime_in_iso8601(timestamp),
         | 
| 282 | 
            -
                      :type => 'page'
         | 
| 283 | 
            -
                    })
         | 
| 126 | 
            +
                    enqueue(FieldParser.parse_for_page(attrs))
         | 
| 284 127 | 
             
                  end
         | 
| 285 128 |  | 
| 286 129 | 
             
                  # Records a screen view (for a mobile app)
         | 
| 287 130 | 
             
                  #
         | 
| 288 131 | 
             
                  # @param [Hash] attrs
         | 
| 289 | 
            -
                  # | 
| 290 | 
            -
                  #   who they are yet. (optional but you must provide either an
         | 
| 291 | 
            -
                  #   `anonymous_id` or `user_id`)
         | 
| 292 | 
            -
                  # @option attrs [String] :category The screen category (optional)
         | 
| 293 | 
            -
                  # @option attrs [Hash] :context ({})
         | 
| 294 | 
            -
                  # @option attrs [Hash] :integrations What integrations this event
         | 
| 295 | 
            -
                  #   goes to (optional)
         | 
| 132 | 
            +
                  #
         | 
| 296 133 | 
             
                  # @option attrs [String] :name Name of the screen
         | 
| 297 | 
            -
                  # @option attrs [Hash] : | 
| 298 | 
            -
                  # @option attrs [ | 
| 299 | 
            -
                  # @ | 
| 300 | 
            -
                  # @option attrs [String] :user_id The ID of the user viewing the screen
         | 
| 301 | 
            -
                  # @option attrs [String] :message_id ID that uniquely identifies a
         | 
| 302 | 
            -
                  #   message across the API. (optional)
         | 
| 134 | 
            +
                  # @option attrs [Hash] :properties Screen properties (optional)
         | 
| 135 | 
            +
                  # @option attrs [String] :category The screen category (optional)
         | 
| 136 | 
            +
                  # @macro common_attrs
         | 
| 303 137 | 
             
                  def screen(attrs)
         | 
| 304 138 | 
             
                    symbolize_keys! attrs
         | 
| 305 | 
            -
                     | 
| 306 | 
            -
             | 
| 307 | 
            -
                    name = attrs[:name].to_s
         | 
| 308 | 
            -
                    properties = attrs[:properties] || {}
         | 
| 309 | 
            -
                    timestamp = attrs[:timestamp] || Time.new
         | 
| 310 | 
            -
                    context = attrs[:context] || {}
         | 
| 311 | 
            -
                    message_id = attrs[:message_id].to_s if attrs[:message_id]
         | 
| 312 | 
            -
             | 
| 313 | 
            -
                    raise ArgumentError, '.properties must be a hash' unless properties.is_a? Hash
         | 
| 314 | 
            -
                    isoify_dates! properties
         | 
| 315 | 
            -
             | 
| 316 | 
            -
                    check_timestamp! timestamp
         | 
| 317 | 
            -
                    add_context context
         | 
| 318 | 
            -
             | 
| 319 | 
            -
                    enqueue({
         | 
| 320 | 
            -
                      :userId => attrs[:user_id],
         | 
| 321 | 
            -
                      :anonymousId => attrs[:anonymous_id],
         | 
| 322 | 
            -
                      :name => name,
         | 
| 323 | 
            -
                      :properties => properties,
         | 
| 324 | 
            -
                      :category => attrs[:category],
         | 
| 325 | 
            -
                      :options => attrs[:options],
         | 
| 326 | 
            -
                      :integrations => attrs[:integrations],
         | 
| 327 | 
            -
                      :context => context,
         | 
| 328 | 
            -
                      :messageId => message_id,
         | 
| 329 | 
            -
                      :timestamp => timestamp.iso8601,
         | 
| 330 | 
            -
                      :type => 'screen'
         | 
| 331 | 
            -
                    })
         | 
| 139 | 
            +
                    enqueue(FieldParser.parse_for_screen(attrs))
         | 
| 332 140 | 
             
                  end
         | 
| 333 141 |  | 
| 334 142 | 
             
                  # @return [Fixnum] number of messages in the queue
         | 
| @@ -336,6 +144,14 @@ module Segment | |
| 336 144 | 
             
                    @queue.length
         | 
| 337 145 | 
             
                  end
         | 
| 338 146 |  | 
| 147 | 
            +
                  def test_queue
         | 
| 148 | 
            +
                    unless @test
         | 
| 149 | 
            +
                      raise 'Test queue only available when setting :test to true.'
         | 
| 150 | 
            +
                    end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                    @test_queue ||= TestQueue.new
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
             | 
| 339 155 | 
             
                  private
         | 
| 340 156 |  | 
| 341 157 | 
             
                  # private: Enqueues the action.
         | 
| @@ -345,6 +161,8 @@ module Segment | |
| 345 161 | 
             
                    # add our request id for tracing purposes
         | 
| 346 162 | 
             
                    action[:messageId] ||= uid
         | 
| 347 163 |  | 
| 164 | 
            +
                    test_queue << action if @test
         | 
| 165 | 
            +
             | 
| 348 166 | 
             
                    if @queue.length < @max_queue_size
         | 
| 349 167 | 
             
                      @queue << action
         | 
| 350 168 | 
             
                      ensure_worker_running
         | 
| @@ -360,53 +178,11 @@ module Segment | |
| 360 178 | 
             
                    end
         | 
| 361 179 | 
             
                  end
         | 
| 362 180 |  | 
| 363 | 
            -
                  # private: Ensures that a string is non-empty
         | 
| 364 | 
            -
                  #
         | 
| 365 | 
            -
                  # obj    - String|Number that must be non-blank
         | 
| 366 | 
            -
                  # name   - Name of the validated value
         | 
| 367 | 
            -
                  #
         | 
| 368 | 
            -
                  def check_presence!(obj, name)
         | 
| 369 | 
            -
                    if obj.nil? || (obj.is_a?(String) && obj.empty?)
         | 
| 370 | 
            -
                      raise ArgumentError, "#{name} must be given"
         | 
| 371 | 
            -
                    end
         | 
| 372 | 
            -
                  end
         | 
| 373 | 
            -
             | 
| 374 | 
            -
                  # private: Adds contextual information to the call
         | 
| 375 | 
            -
                  #
         | 
| 376 | 
            -
                  # context - Hash of call context
         | 
| 377 | 
            -
                  def add_context(context)
         | 
| 378 | 
            -
                    context[:library] = { :name => 'analytics-ruby', :version => Segment::Analytics::VERSION.to_s }
         | 
| 379 | 
            -
                  end
         | 
| 380 | 
            -
             | 
| 381 181 | 
             
                  # private: Checks that the write_key is properly initialized
         | 
| 382 182 | 
             
                  def check_write_key!
         | 
| 383 183 | 
             
                    raise ArgumentError, 'Write key must be initialized' if @write_key.nil?
         | 
| 384 184 | 
             
                  end
         | 
| 385 185 |  | 
| 386 | 
            -
                  # private: Checks the timstamp option to make sure it is a Time.
         | 
| 387 | 
            -
                  def check_timestamp!(timestamp)
         | 
| 388 | 
            -
                    raise ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time
         | 
| 389 | 
            -
                  end
         | 
| 390 | 
            -
             | 
| 391 | 
            -
                  def event(attrs)
         | 
| 392 | 
            -
                    symbolize_keys! attrs
         | 
| 393 | 
            -
             | 
| 394 | 
            -
                    {
         | 
| 395 | 
            -
                      :userId => user_id,
         | 
| 396 | 
            -
                      :name => name,
         | 
| 397 | 
            -
                      :properties => properties,
         | 
| 398 | 
            -
                      :context => context,
         | 
| 399 | 
            -
                      :timestamp => datetime_in_iso8601(timestamp),
         | 
| 400 | 
            -
                      :type => 'screen'
         | 
| 401 | 
            -
                    }
         | 
| 402 | 
            -
                  end
         | 
| 403 | 
            -
             | 
| 404 | 
            -
                  def check_user_id!(attrs)
         | 
| 405 | 
            -
                    unless attrs[:user_id] || attrs[:anonymous_id]
         | 
| 406 | 
            -
                      raise ArgumentError, 'Must supply either user_id or anonymous_id'
         | 
| 407 | 
            -
                    end
         | 
| 408 | 
            -
                  end
         | 
| 409 | 
            -
             | 
| 410 186 | 
             
                  def ensure_worker_running
         | 
| 411 187 | 
             
                    return if worker_running?
         | 
| 412 188 | 
             
                    @worker_mutex.synchronize do
         | 
| @@ -0,0 +1,192 @@ | |
| 1 | 
            +
            module Segment
         | 
| 2 | 
            +
              class Analytics
         | 
| 3 | 
            +
                # Handles parsing fields according to the Segment Spec
         | 
| 4 | 
            +
                #
         | 
| 5 | 
            +
                # @see https://segment.com/docs/spec/
         | 
| 6 | 
            +
                class FieldParser
         | 
| 7 | 
            +
                  class << self
         | 
| 8 | 
            +
                    include Segment::Analytics::Utils
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    # In addition to the common fields, track accepts:
         | 
| 11 | 
            +
                    #
         | 
| 12 | 
            +
                    # - "event"
         | 
| 13 | 
            +
                    # - "properties"
         | 
| 14 | 
            +
                    def parse_for_track(fields)
         | 
| 15 | 
            +
                      common = parse_common_fields(fields)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                      event = fields[:event]
         | 
| 18 | 
            +
                      properties = fields[:properties] || {}
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                      check_presence!(event, 'event')
         | 
| 21 | 
            +
                      check_is_hash!(properties, 'properties')
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                      isoify_dates! properties
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      common.merge({
         | 
| 26 | 
            +
                        :type => 'track',
         | 
| 27 | 
            +
                        :event => event.to_s,
         | 
| 28 | 
            +
                        :properties => properties
         | 
| 29 | 
            +
                      })
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    # In addition to the common fields, identify accepts:
         | 
| 33 | 
            +
                    #
         | 
| 34 | 
            +
                    # - "traits"
         | 
| 35 | 
            +
                    def parse_for_identify(fields)
         | 
| 36 | 
            +
                      common = parse_common_fields(fields)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                      traits = fields[:traits] || {}
         | 
| 39 | 
            +
                      check_is_hash!(traits, 'traits')
         | 
| 40 | 
            +
                      isoify_dates! traits
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                      common.merge({
         | 
| 43 | 
            +
                        :type => 'identify',
         | 
| 44 | 
            +
                        :traits => traits
         | 
| 45 | 
            +
                      })
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    # In addition to the common fields, alias accepts:
         | 
| 49 | 
            +
                    #
         | 
| 50 | 
            +
                    # - "previous_id"
         | 
| 51 | 
            +
                    def parse_for_alias(fields)
         | 
| 52 | 
            +
                      common = parse_common_fields(fields)
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                      previous_id = fields[:previous_id]
         | 
| 55 | 
            +
                      check_presence!(previous_id, 'previous_id')
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      common.merge({
         | 
| 58 | 
            +
                        :type => 'alias',
         | 
| 59 | 
            +
                        :previousId => previous_id
         | 
| 60 | 
            +
                      })
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    # In addition to the common fields, group accepts:
         | 
| 64 | 
            +
                    #
         | 
| 65 | 
            +
                    # - "group_id"
         | 
| 66 | 
            +
                    # - "traits"
         | 
| 67 | 
            +
                    def parse_for_group(fields)
         | 
| 68 | 
            +
                      common = parse_common_fields(fields)
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                      group_id = fields[:group_id]
         | 
| 71 | 
            +
                      traits = fields[:traits] || {}
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                      check_presence!(group_id, 'group_id')
         | 
| 74 | 
            +
                      check_is_hash!(traits, 'traits')
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                      isoify_dates! traits
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                      common.merge({
         | 
| 79 | 
            +
                        :type => 'group',
         | 
| 80 | 
            +
                        :groupId => group_id,
         | 
| 81 | 
            +
                        :traits => traits
         | 
| 82 | 
            +
                      })
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    # In addition to the common fields, page accepts:
         | 
| 86 | 
            +
                    #
         | 
| 87 | 
            +
                    # - "name"
         | 
| 88 | 
            +
                    # - "properties"
         | 
| 89 | 
            +
                    def parse_for_page(fields)
         | 
| 90 | 
            +
                      common = parse_common_fields(fields)
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                      name = fields[:name] || ''
         | 
| 93 | 
            +
                      properties = fields[:properties] || {}
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                      check_is_hash!(properties, 'properties')
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                      isoify_dates! properties
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                      common.merge({
         | 
| 100 | 
            +
                        :type => 'page',
         | 
| 101 | 
            +
                        :name => name.to_s,
         | 
| 102 | 
            +
                        :properties => properties
         | 
| 103 | 
            +
                      })
         | 
| 104 | 
            +
                    end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    # In addition to the common fields, screen accepts:
         | 
| 107 | 
            +
                    #
         | 
| 108 | 
            +
                    # - "name"
         | 
| 109 | 
            +
                    # - "properties"
         | 
| 110 | 
            +
                    # - "category" (Not in spec, retained for backward compatibility"
         | 
| 111 | 
            +
                    def parse_for_screen(fields)
         | 
| 112 | 
            +
                      common = parse_common_fields(fields)
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                      name = fields[:name]
         | 
| 115 | 
            +
                      properties = fields[:properties] || {}
         | 
| 116 | 
            +
                      category = fields[:category]
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                      check_presence!(name, 'name')
         | 
| 119 | 
            +
                      check_is_hash!(properties, 'properties')
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                      isoify_dates! properties
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                      parsed = common.merge({
         | 
| 124 | 
            +
                        :type => 'screen',
         | 
| 125 | 
            +
                        :name => name,
         | 
| 126 | 
            +
                        :properties => properties
         | 
| 127 | 
            +
                      })
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                      parsed[:category] = category if category
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                      parsed
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                    private
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                    def parse_common_fields(fields)
         | 
| 137 | 
            +
                      timestamp = fields[:timestamp] || Time.new
         | 
| 138 | 
            +
                      message_id = fields[:message_id].to_s if fields[:message_id]
         | 
| 139 | 
            +
                      context = fields[:context] || {}
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                      check_user_id! fields
         | 
| 142 | 
            +
                      check_timestamp! timestamp
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                      add_context! context
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                      parsed = {
         | 
| 147 | 
            +
                        :context => context,
         | 
| 148 | 
            +
                        :messageId => message_id,
         | 
| 149 | 
            +
                        :timestamp => datetime_in_iso8601(timestamp)
         | 
| 150 | 
            +
                      }
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                      parsed[:userId] = fields[:user_id] if fields[:user_id]
         | 
| 153 | 
            +
                      parsed[:anonymousId] = fields[:anonymous_id] if fields[:anonymous_id]
         | 
| 154 | 
            +
                      parsed[:integrations] = fields[:integrations] if fields[:integrations]
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                      # Not in spec, retained for backward compatibility
         | 
| 157 | 
            +
                      parsed[:options] = fields[:options] if fields[:options]
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                      parsed
         | 
| 160 | 
            +
                    end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                    def check_user_id!(fields)
         | 
| 163 | 
            +
                      unless fields[:user_id] || fields[:anonymous_id]
         | 
| 164 | 
            +
                        raise ArgumentError, 'Must supply either user_id or anonymous_id'
         | 
| 165 | 
            +
                      end
         | 
| 166 | 
            +
                    end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                    def check_timestamp!(timestamp)
         | 
| 169 | 
            +
                      raise ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time
         | 
| 170 | 
            +
                    end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                    def add_context!(context)
         | 
| 173 | 
            +
                      context[:library] = { :name => 'analytics-ruby', :version => Segment::Analytics::VERSION.to_s }
         | 
| 174 | 
            +
                    end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                    # private: Ensures that a string is non-empty
         | 
| 177 | 
            +
                    #
         | 
| 178 | 
            +
                    # obj    - String|Number that must be non-blank
         | 
| 179 | 
            +
                    # name   - Name of the validated value
         | 
| 180 | 
            +
                    def check_presence!(obj, name)
         | 
| 181 | 
            +
                      if obj.nil? || (obj.is_a?(String) && obj.empty?)
         | 
| 182 | 
            +
                        raise ArgumentError, "#{name} must be given"
         | 
| 183 | 
            +
                      end
         | 
| 184 | 
            +
                    end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                    def check_is_hash!(obj, name)
         | 
| 187 | 
            +
                      raise ArgumentError, "#{name} must be a Hash" unless obj.is_a? Hash
         | 
| 188 | 
            +
                    end
         | 
| 189 | 
            +
                  end
         | 
| 190 | 
            +
                end
         | 
| 191 | 
            +
              end
         | 
| 192 | 
            +
            end
         |