readme-metrics 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 00a1c3d34d8c61c9fbcaacc3914ddb3e03837ce22bd3325dbabd30ba5b4c373e
4
+ data.tar.gz: 34665a455715eaf423370cba89b31575dc87d53169c9bb8680f8b87a847e1704
5
+ SHA512:
6
+ metadata.gz: '05980a5c1bbdb0b23d438f18c8fcb27f931e3781e2e41ce938dff13611bfc6b70d8c61c141f644d4eeb3b1381019a3eff38e52da2adeb223cd1761ba8b6e53e7'
7
+ data.tar.gz: 0a084c22580cc59868089773b85dec83ad295c1984ac616f1ad8a947a8914130f35c19c78bf05b8945188cddbf49882391a8375581c5f5f50d0af0531e9db2ea
@@ -0,0 +1,2 @@
1
+ .rspec_status
2
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in readme-metrics.gemspec
6
+ gemspec
7
+
8
+ gem "json-schema"
9
+ gem "rack-test"
10
+ gem "rake", "~> 12.0"
11
+ gem "rspec", "~> 3.0"
12
+ gem "standard"
13
+ gem "webmock"
@@ -0,0 +1,87 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ readme-metrics (0.1.0)
5
+ httparty (~> 0.18)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ addressable (2.7.0)
11
+ public_suffix (>= 2.0.2, < 5.0)
12
+ ast (2.4.1)
13
+ crack (0.4.3)
14
+ safe_yaml (~> 1.0.0)
15
+ diff-lcs (1.4.4)
16
+ hashdiff (1.0.1)
17
+ httparty (0.18.1)
18
+ mime-types (~> 3.0)
19
+ multi_xml (>= 0.5.2)
20
+ json-schema (2.8.1)
21
+ addressable (>= 2.4)
22
+ mime-types (3.3.1)
23
+ mime-types-data (~> 3.2015)
24
+ mime-types-data (3.2020.0512)
25
+ multi_xml (0.6.0)
26
+ parallel (1.19.2)
27
+ parser (2.7.1.4)
28
+ ast (~> 2.4.1)
29
+ public_suffix (4.0.5)
30
+ rack (2.2.3)
31
+ rack-test (1.1.0)
32
+ rack (>= 1.0, < 3)
33
+ rainbow (3.0.0)
34
+ rake (12.3.3)
35
+ regexp_parser (1.7.1)
36
+ rexml (3.2.4)
37
+ rspec (3.9.0)
38
+ rspec-core (~> 3.9.0)
39
+ rspec-expectations (~> 3.9.0)
40
+ rspec-mocks (~> 3.9.0)
41
+ rspec-core (3.9.2)
42
+ rspec-support (~> 3.9.3)
43
+ rspec-expectations (3.9.2)
44
+ diff-lcs (>= 1.2.0, < 2.0)
45
+ rspec-support (~> 3.9.0)
46
+ rspec-mocks (3.9.1)
47
+ diff-lcs (>= 1.2.0, < 2.0)
48
+ rspec-support (~> 3.9.0)
49
+ rspec-support (3.9.3)
50
+ rubocop (0.85.1)
51
+ parallel (~> 1.10)
52
+ parser (>= 2.7.0.1)
53
+ rainbow (>= 2.2.2, < 4.0)
54
+ regexp_parser (>= 1.7)
55
+ rexml
56
+ rubocop-ast (>= 0.0.3)
57
+ ruby-progressbar (~> 1.7)
58
+ unicode-display_width (>= 1.4.0, < 2.0)
59
+ rubocop-ast (0.3.0)
60
+ parser (>= 2.7.1.4)
61
+ rubocop-performance (1.6.1)
62
+ rubocop (>= 0.71.0)
63
+ ruby-progressbar (1.10.1)
64
+ safe_yaml (1.0.5)
65
+ standard (0.4.7)
66
+ rubocop (~> 0.85.0)
67
+ rubocop-performance (~> 1.6.0)
68
+ unicode-display_width (1.7.0)
69
+ webmock (3.8.3)
70
+ addressable (>= 2.3.6)
71
+ crack (>= 0.3.2)
72
+ hashdiff (>= 0.4.0, < 2.0.0)
73
+
74
+ PLATFORMS
75
+ ruby
76
+
77
+ DEPENDENCIES
78
+ json-schema
79
+ rack-test
80
+ rake (~> 12.0)
81
+ readme-metrics!
82
+ rspec (~> 3.0)
83
+ standard
84
+ webmock
85
+
86
+ BUNDLED WITH
87
+ 2.1.4
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2020, ReadMe
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose
4
+ with or without fee is hereby granted, provided that the above copyright notice
5
+ and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
9
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
11
+ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
12
+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
13
+ THIS SOFTWARE.
@@ -0,0 +1,94 @@
1
+ # readmeio
2
+
3
+ Track your API metrics within ReadMe.
4
+
5
+ [![](https://d3vv6lp55qjaqc.cloudfront.net/items/1M3C3j0I0s0j3T362344/Untitled-2.png)](https://readme.io)
6
+
7
+ ## Installation
8
+
9
+ Add it to your Gemfile
10
+
11
+ `gem "readme-metrics"`
12
+
13
+ ## Usage
14
+
15
+ `Readme::Metrics` is a Rack middleware and is compatible with all Rack-based
16
+ apps, including Rails.
17
+
18
+ When configuring the middleware, you must provide a block to tell the
19
+ middleware how to get values for the current user. These may be values taken
20
+ from the environment, or you may hardcode them.
21
+
22
+ If you're using Warden-based authentication like Devise, you may fetch the
23
+ current_user for a given request from the environment.
24
+
25
+ ### Batching requests
26
+
27
+ By default, the middleware will batch requests to the ReadMe API in groups of
28
+ 10. For every 10 requests made to your application, the middleware will make a
29
+ single request to ReadMe. If you wish to override this, provide a
30
+ `buffer_length` option when configuring the middleware.
31
+
32
+ ### Sensitive Data
33
+
34
+ If you have sensitive data you'd like to prevent from being sent to the Metrics
35
+ API via headers, query params or payload bodies, you can specify a list of keys
36
+ to filter via the `reject_params` option. Key-value pairs matching these keys
37
+ will not be included in the request to the Metrics API.
38
+
39
+ You are also able to specify a set of `allow_only` which should only be sent through.
40
+ Any header or body values not matching these keys will be filtered out and not
41
+ send to the API.
42
+
43
+ You may only specify either `reject_params` or `allow_only` keys, not both.
44
+
45
+ ### Rails
46
+
47
+ ```ruby
48
+ # application.rb
49
+ require "readme/metrics"
50
+
51
+ options = {
52
+ api_key: "YOUR_API_KEY",
53
+ development: false,
54
+ reject_params: ["not_included", "dont_send"],
55
+ buffer_length: 5,
56
+ }
57
+
58
+ config.middleware.use Readme::Metrics, options do |env|
59
+ current_user = env['warden'].authenticate(scope: :current_user)
60
+
61
+ {
62
+ id: current_user.id
63
+ label: current_user.full_name,
64
+ email: current_user.email
65
+ }
66
+ end
67
+ ```
68
+
69
+ ### Rack::Builder
70
+
71
+ ```ruby
72
+ Rack::Builder.new do |builder|
73
+ options = {
74
+ api_key: "YOUR_API_KEY",
75
+ development: false,
76
+ reject_params: ["not_included", "dont_send"]
77
+ }
78
+
79
+ builder.use Readme::Metrics, options do |env|
80
+ {
81
+ id: "my_application_id"
82
+ label: "My Application",
83
+ email: "my.application@example.com"
84
+ }
85
+ end
86
+ builder.run your_app
87
+ end
88
+ ```
89
+
90
+ ## License
91
+
92
+ [View our license here](https://github.com/readmeio/metrics-sdks/tree/master/packages/ruby/LICENSE)
93
+
94
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "readme/metrics"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,84 @@
1
+ require "rack"
2
+ require "rack/request"
3
+
4
+ class HttpRequest
5
+ HTTP_NON_HEADERS = [
6
+ Rack::HTTP_COOKIE,
7
+ Rack::HTTP_VERSION,
8
+ Rack::HTTP_HOST,
9
+ Rack::HTTP_PORT
10
+ ]
11
+
12
+ def initialize(env)
13
+ @request = Rack::Request.new(env)
14
+ end
15
+
16
+ def url
17
+ @request.url
18
+ end
19
+
20
+ def query_params
21
+ @request.GET
22
+ end
23
+
24
+ def cookies
25
+ @request.cookies
26
+ end
27
+
28
+ def http_version
29
+ @request.get_header(Rack::HTTP_VERSION)
30
+ end
31
+
32
+ def request_method
33
+ @request.request_method
34
+ end
35
+
36
+ def content_type
37
+ @request.content_type
38
+ end
39
+
40
+ def form_data?
41
+ @request.form_data?
42
+ end
43
+
44
+ def content_length
45
+ @request.content_length.to_i
46
+ end
47
+
48
+ def headers
49
+ @request
50
+ .each_header
51
+ .select { |key, _| http_header?(key) }
52
+ .to_h
53
+ .transform_keys { |header| normalize_header_name(header) }
54
+ end
55
+
56
+ def body
57
+ @request.body.rewind
58
+ content = @request.body.read
59
+ @request.body.rewind
60
+
61
+ content
62
+ end
63
+
64
+ def parsed_form_data
65
+ @request.POST
66
+ end
67
+
68
+ private
69
+
70
+ # "headers" in Rack::Request just means any key in the env. The HTTP headers
71
+ # are all the headers prefixed with `HTTP_` as per the spec:
72
+ # https://github.com/rack/rack/blob/master/SPEC.rdoc#the-environment-
73
+ # Other "headers" like version and host are prefixed with `HTTP_` by Rack but
74
+ # don't seem to be considered legit HTTP headers.
75
+ def http_header?(name)
76
+ name.start_with?("HTTP") && !HTTP_NON_HEADERS.include?(name)
77
+ end
78
+
79
+ # Headers like `Content-Type: application/json` come into rack like
80
+ # `"HTTP_CONTENT_TYPE" => "application/json"`.
81
+ def normalize_header_name(header)
82
+ header.delete_prefix("HTTP_").split("_").map(&:capitalize).join("-")
83
+ end
84
+ end
@@ -0,0 +1,33 @@
1
+ module Readme
2
+ class Errors
3
+ API_KEY_ERROR = "Missing API Key"
4
+ REJECT_PARAMS_ERROR = "The `reject_params` option must be an array of strings"
5
+ ALLOW_ONLY_ERROR = "The `allow_only` option must be an array of strings"
6
+ BUFFER_LENGTH_ERROR = "The `buffer_length` must be an Integer"
7
+ DEVELOPMENT_ERROR = "The `development` option must be a boolean"
8
+
9
+ MISSING_BLOCK_ERROR = <<~MESSAGE
10
+ Missing block argument. You must provide a block when configuring the
11
+ middleware. The block must return a hash containing user info:
12
+ use Readme::Metrics, options do |env|
13
+ { id: "unique_id", label: "Your user label", email: "Your user email" }
14
+ end
15
+ MESSAGE
16
+
17
+ def self.bad_block_message(result)
18
+ <<~MESSAGE
19
+ The request could not be sent to Readme. Something went wrong when
20
+ setting user info. Double check the block configured on the ReadMe
21
+ middleware.
22
+
23
+ Expected a hash with the shape:
24
+ { id: "unique_id", label: "Your user label", email: "Your user email" }
25
+
26
+ Received value:
27
+ #{result}
28
+ MESSAGE
29
+ end
30
+
31
+ class ConfigurationError < StandardError; end
32
+ end
33
+ end
@@ -0,0 +1,46 @@
1
+ class Filter
2
+ def self.for(reject: nil, allow_only: nil)
3
+ if !reject.nil? && !allow_only.nil?
4
+ raise FilterArgsError
5
+ elsif !reject.nil?
6
+ RejectParams.new(reject)
7
+ elsif !allow_only.nil?
8
+ AllowOnly.new(allow_only)
9
+ else
10
+ None.new
11
+ end
12
+ end
13
+
14
+ class AllowOnly
15
+ def initialize(filter_values)
16
+ @filter_values = filter_values
17
+ end
18
+
19
+ def filter(hash)
20
+ hash.select { |key, _value| @filter_values.include?(key) }
21
+ end
22
+ end
23
+
24
+ class RejectParams
25
+ def initialize(filter_values)
26
+ @filter_values = filter_values
27
+ end
28
+
29
+ def filter(hash)
30
+ hash.reject { |key, _value| @filter_values.include?(key) }
31
+ end
32
+ end
33
+
34
+ class None
35
+ def filter(hash)
36
+ hash
37
+ end
38
+ end
39
+
40
+ class FilterArgsError < StandardError
41
+ def initialize
42
+ msg = "Can only supply either reject_params or allow_only, not both."
43
+ super(msg)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,24 @@
1
+ module Readme
2
+ module Har
3
+ class Collection
4
+ def initialize(filter, hash)
5
+ @filter = filter
6
+ @hash = hash
7
+ end
8
+
9
+ def to_h
10
+ filtered_hash
11
+ end
12
+
13
+ def to_a
14
+ filtered_hash.map { |name, value| {name: name, value: value} }
15
+ end
16
+
17
+ private
18
+
19
+ def filtered_hash
20
+ @filter.filter(@hash)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,56 @@
1
+ require "readme/har/collection"
2
+ require "readme/filter"
3
+
4
+ module Readme
5
+ module Har
6
+ class RequestSerializer
7
+ def initialize(request, filter = Filter::None.new)
8
+ @request = request
9
+ @filter = filter
10
+ end
11
+
12
+ def as_json
13
+ {
14
+ method: @request.request_method,
15
+ queryString: Har::Collection.new(@filter, @request.query_params).to_a,
16
+ url: @request.url,
17
+ httpVersion: @request.http_version,
18
+ headers: Har::Collection.new(@filter, @request.headers).to_a,
19
+ cookies: Har::Collection.new(@filter, @request.cookies).to_a,
20
+ postData: postData,
21
+ headersSize: -1,
22
+ bodySize: @request.content_length
23
+ }.compact
24
+ end
25
+
26
+ private
27
+
28
+ def postData
29
+ if @request.content_type.nil?
30
+ nil
31
+ elsif @request.form_data?
32
+ {
33
+ params: form_encoded_body,
34
+ mimeType: @request.content_type
35
+ }
36
+ else
37
+ {
38
+ text: request_body,
39
+ mimeType: @request.content_type
40
+ }
41
+ end
42
+ end
43
+
44
+ def form_encoded_body
45
+ Har::Collection.new(@filter, @request.parsed_form_data).to_a
46
+ end
47
+
48
+ def request_body
49
+ parsed_body = JSON.parse(@request.body)
50
+ Har::Collection.new(@filter, parsed_body).to_h.to_json
51
+ rescue
52
+ @request.body
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,80 @@
1
+ require "rack/utils"
2
+ require "readme/har/collection"
3
+
4
+ module Readme
5
+ module Har
6
+ class ResponseSerializer
7
+ JSON_MIME_TYPES = ["application/json", "application/x-json", "text/json", "text/x-json", "+json"]
8
+
9
+ def initialize(request, response, filter)
10
+ @request = request
11
+ @response = response
12
+ @filter = filter
13
+ end
14
+
15
+ def as_json
16
+ {
17
+ status: @response.status,
18
+ statusText: Rack::Utils::HTTP_STATUS_CODES[@response.status],
19
+ httpVersion: @request.http_version,
20
+ headers: Har::Collection.new(@filter, @response.headers).to_a,
21
+ content: content,
22
+ redirectURL: @response.location.to_s,
23
+ headersSize: -1,
24
+ bodySize: @response.content_length,
25
+ cookies: Har::Collection.new(@filter, @request.cookies).to_a
26
+ }
27
+ end
28
+
29
+ private
30
+
31
+ def content
32
+ if response_body.nil?
33
+ empty_content
34
+ elsif content_type_is_json?
35
+ json_content
36
+ else
37
+ pass_through_content
38
+ end
39
+ end
40
+
41
+ def empty_content
42
+ {mimeType: "", size: 0}
43
+ end
44
+
45
+ def json_content
46
+ parsed_body = JSON.parse(response_body)
47
+
48
+ {mimeType: @response.content_type,
49
+ size: @response.content_length,
50
+ text: Har::Collection.new(@filter, parsed_body).to_h.to_json}
51
+ rescue
52
+ pass_through_content
53
+ end
54
+
55
+ def pass_through_content
56
+ {mimeType: @response.content_type,
57
+ size: @response.content_length,
58
+ text: response_body}
59
+ end
60
+
61
+ def response_body
62
+ if @response.body.nil?
63
+ nil
64
+ elsif @response.body.respond_to?(:rewind)
65
+ @response.body.rewind
66
+ body = @response.body.each.reduce(:+)
67
+ @response.body.rewind
68
+
69
+ body
70
+ else
71
+ @response.body.each.reduce(:+)
72
+ end
73
+ end
74
+
75
+ def content_type_is_json?
76
+ JSON_MIME_TYPES.include? @response.content_type
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,74 @@
1
+ require "rack"
2
+ require "http_request"
3
+ require "readme/metrics"
4
+ require "readme/har/request_serializer"
5
+ require "readme/har/response_serializer"
6
+ require "readme/har/collection"
7
+
8
+ module Readme
9
+ module Har
10
+ class Serializer
11
+ HAR_VERSION = "1.2"
12
+
13
+ def initialize(env, response, start_time, end_time, filter)
14
+ @http_request = HttpRequest.new(env)
15
+ @response = response
16
+ @start_time = start_time
17
+ @end_time = end_time
18
+ @filter = filter
19
+ end
20
+
21
+ def to_json
22
+ {
23
+ log: {
24
+ version: HAR_VERSION,
25
+ creator: creator,
26
+ entries: entries
27
+ }
28
+ }.to_json
29
+ end
30
+
31
+ private
32
+
33
+ def creator
34
+ {
35
+ name: Readme::Metrics::SDK_NAME,
36
+ version: Readme::Metrics::VERSION
37
+ }
38
+ end
39
+
40
+ def entries
41
+ [
42
+ {
43
+ cache: {},
44
+ timings: timings,
45
+ request: request,
46
+ response: response,
47
+ startedDateTime: @start_time.iso8601,
48
+ time: elapsed_time
49
+ }
50
+ ]
51
+ end
52
+
53
+ def timings
54
+ {
55
+ send: 0,
56
+ receive: 0,
57
+ wait: elapsed_time
58
+ }
59
+ end
60
+
61
+ def elapsed_time
62
+ ((@end_time - @start_time) * 1000).to_i
63
+ end
64
+
65
+ def request
66
+ Har::RequestSerializer.new(@http_request, @filter).as_json
67
+ end
68
+
69
+ def response
70
+ Har::ResponseSerializer.new(@http_request, @response, @filter).as_json
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,86 @@
1
+ require "readme/metrics/version"
2
+ require "readme/har/serializer"
3
+ require "readme/filter"
4
+ require "readme/payload"
5
+ require "readme/request_queue"
6
+ require "readme/errors"
7
+ require "httparty"
8
+
9
+ module Readme
10
+ class Metrics
11
+ SDK_NAME = "Readme.io Ruby SDK"
12
+ DEFAULT_BUFFER_LENGTH = 10
13
+ ENDPOINT = "https://metrics.readme.io/v1/request"
14
+ USER_INFO_KEYS = [:id, :label, :email]
15
+
16
+ def initialize(app, options, &get_user_info)
17
+ validate_options(options)
18
+ raise Errors::ConfigurationError, Errors::MISSING_BLOCK_ERROR if get_user_info.nil?
19
+
20
+ @app = app
21
+ @development = options[:development] || false
22
+ @filter = Filter.for(
23
+ reject: options[:reject_params],
24
+ allow_only: options[:allow_only]
25
+ )
26
+ @get_user_info = get_user_info
27
+
28
+ buffer_length = options[:buffer_length] || DEFAULT_BUFFER_LENGTH
29
+ @@request_queue = Readme::RequestQueue.new(options[:api_key], buffer_length)
30
+ end
31
+
32
+ def call(env)
33
+ start_time = Time.now
34
+ status, headers, body = @app.call(env)
35
+ end_time = Time.now
36
+
37
+ response = Rack::Response.new(body, status, headers)
38
+
39
+ har = Har::Serializer.new(env, response, start_time, end_time, @filter)
40
+
41
+ user_info = @get_user_info.call(env)
42
+
43
+ if user_info_invalid?(user_info)
44
+ puts Errors.bad_block_message(user_info)
45
+ return [status, headers, body]
46
+ else
47
+ payload = Payload.new(har, user_info, development: @development)
48
+ @@request_queue.push(payload.to_json)
49
+ end
50
+
51
+ [status, headers, body]
52
+ end
53
+
54
+ private
55
+
56
+ def validate_options(options)
57
+ raise Errors::ConfigurationError, Errors::API_KEY_ERROR if options[:api_key].nil?
58
+
59
+ if options[:reject_params]&.any? { |param| !param.is_a? String }
60
+ raise Errors::ConfigurationError, Errors::REJECT_PARAMS_ERROR
61
+ end
62
+
63
+ if options[:allow_only]&.any? { |param| !param.is_a? String }
64
+ raise Errors::ConfigurationError, Errors::ALLOW_ONLY_ERROR
65
+ end
66
+
67
+ if options[:buffer_length] && !options[:buffer_length].is_a?(Integer)
68
+ raise Errors::ConfigurationError, Errors::BUFFER_LENGTH_ERROR
69
+ end
70
+
71
+ if options[:development] && !is_a_boolean?(options[:development])
72
+ raise Errors::ConfigurationError, Errors::DEVELOPMENT_ERROR
73
+ end
74
+ end
75
+
76
+ def is_a_boolean?(arg)
77
+ arg == true || arg == false
78
+ end
79
+
80
+ def user_info_invalid?(user_info)
81
+ user_info.nil? ||
82
+ user_info.values.any?(&:nil?) ||
83
+ USER_INFO_KEYS.sort != user_info.keys.sort
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,5 @@
1
+ module Readme
2
+ class Metrics
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ module Readme
2
+ class Payload
3
+ def initialize(har, user_info, development:)
4
+ @har = har
5
+ @user_info = user_info
6
+ @development = development
7
+ end
8
+
9
+ def to_json
10
+ {
11
+ group: @user_info,
12
+ clientIPAddress: "1.1.1.1",
13
+ development: @development,
14
+ request: JSON.parse(@har.to_json)
15
+ }.to_json
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ require "readme/metrics"
2
+
3
+ module Readme
4
+ class RequestQueue
5
+ attr_reader :queue
6
+
7
+ def initialize(api_key, buffer_length)
8
+ @queue = []
9
+ @buffer_length = buffer_length
10
+ @api_key = api_key
11
+ end
12
+
13
+ def push(request)
14
+ @queue << request
15
+
16
+ if ready_to_send?
17
+ payloads = @queue.slice!(0, @buffer_length)
18
+ HTTParty.post(
19
+ Readme::Metrics::ENDPOINT,
20
+ basic_auth: {username: @api_key, password: ""},
21
+ headers: {"Content-Type" => "application/json"},
22
+ body: to_json(payloads)
23
+ )
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def ready_to_send?
30
+ @queue.length >= @buffer_length
31
+ end
32
+
33
+ def to_json(payloads)
34
+ "[#{payloads.join(", ")}]"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "lib/readme/metrics/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "readme-metrics"
5
+ spec.version = Readme::Metrics::VERSION
6
+ spec.authors = ["ReadMe"]
7
+ spec.email = ["support@readme.io"]
8
+ spec.license = "ISC"
9
+
10
+ spec.summary = "SDK for Readme's metrics API"
11
+ spec.description = "Middleware for logging requests to Readme's metrics API"
12
+ spec.homepage = "https://docs.readme.com/metrics/docs/getting-started-with-api-metrics"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/readmeio/metrics-sdks/blob/master/packages/ruby"
17
+ spec.metadata["changelog_uri"] = "https://github.com/readmeio/metrics-sdks/blob/master/packages/ruby/CHANGELOG.md"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ # spec.bindir = "exe"
25
+ # spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_runtime_dependency "httparty", "~> 0.18"
29
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: readme-metrics
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ReadMe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-08-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.18'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.18'
27
+ description: Middleware for logging requests to Readme's metrics API
28
+ email:
29
+ - support@readme.io
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - ".rspec"
36
+ - Gemfile
37
+ - Gemfile.lock
38
+ - LICENSE
39
+ - README.md
40
+ - Rakefile
41
+ - bin/console
42
+ - bin/setup
43
+ - lib/http_request.rb
44
+ - lib/readme/errors.rb
45
+ - lib/readme/filter.rb
46
+ - lib/readme/har/collection.rb
47
+ - lib/readme/har/request_serializer.rb
48
+ - lib/readme/har/response_serializer.rb
49
+ - lib/readme/har/serializer.rb
50
+ - lib/readme/metrics.rb
51
+ - lib/readme/metrics/version.rb
52
+ - lib/readme/payload.rb
53
+ - lib/readme/request_queue.rb
54
+ - readme-metrics.gemspec
55
+ homepage: https://docs.readme.com/metrics/docs/getting-started-with-api-metrics
56
+ licenses:
57
+ - ISC
58
+ metadata:
59
+ homepage_uri: https://docs.readme.com/metrics/docs/getting-started-with-api-metrics
60
+ source_code_uri: https://github.com/readmeio/metrics-sdks/blob/master/packages/ruby
61
+ changelog_uri: https://github.com/readmeio/metrics-sdks/blob/master/packages/ruby/CHANGELOG.md
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 2.7.0
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.1.2
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: SDK for Readme's metrics API
81
+ test_files: []