readme-metrics 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []