delighted 1.8.0 → 2.0.0rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17fbb642fdf92f20cf5150c683672f285b346dbc47f2299a16324c05ba916cb1
4
- data.tar.gz: 9f01f27d82dfcc7ceb9566aeeb1b54fde0bea5269cfbf06263707b023b39d2d7
3
+ metadata.gz: 935f134ce42d4fd9df625582e5983ca52097c1025266d1b12bdc0e3a8d1c6a82
4
+ data.tar.gz: 1c05ac2216de6f94213ba2fd47f5d039af4ad751a342495524af59b162ed7161
5
5
  SHA512:
6
- metadata.gz: a1fd60896320ae26991069d5f4287b90dfad66954ab049db110c2624c2f771c81b87563faa042d4517a8138235d121eaffa02fae5a0ba08aab88c2d04ebd8e95
7
- data.tar.gz: a01145982fbc8b3ce25d5bbc19799f037b66caf84ac1a9b34c0a0dc6beb8eedc43c55edfb9ace154558c638ca9fd94aab5c42ff20887aa5bbd5f6095b154ae6a
6
+ metadata.gz: a799887d2859ceb0efa8f3fa72be34c3fc25ecca0f681616ecab1a2d5eed711dea5056bd0077676ce6681cf6125f79abcb01c1e09e68c893e3630d703ef09399
7
+ data.tar.gz: 9f6b6c2d5443e892c75c733f3b7e466d5cfbf21aeef923e0a87d3d5db85c35d744b9a1f8f45deb3fd802a06e27cb0a5bdf693e888535bf77a5e622c453caf356
@@ -1,12 +1,11 @@
1
1
  language: ruby
2
2
  before_install: gem update bundler
3
3
  rvm:
4
+ - ruby-head
5
+ - 2.5
4
6
  - 2.2
5
7
  - 2.1
6
8
  - 2.0
7
9
  - 1.9.3
8
- - 1.9.2
9
- - 1.8.7
10
10
  - jruby-18mode
11
11
  - jruby-19mode
12
- - ree
@@ -1,3 +1,14 @@
1
+ ## 2.0.0rc1 (Unreleased)
2
+
3
+ Features:
4
+
5
+ - Add `Delighted::Person.list`
6
+
7
+ Compatibility changes:
8
+
9
+ - Add support for Ruby 2.7
10
+ - Drop support for Ruby 1.8.7, 1.9.2, and ree
11
+
1
12
  ## 1.8.0 (2018-05-22)
2
13
 
3
14
  Features:
data/README.md CHANGED
@@ -49,6 +49,28 @@ updated_person1 = Delighted::Person.create(:email => "foo+test1@delighted.com",
49
49
  :name => "James Scott", :send => false)
50
50
  ```
51
51
 
52
+ Listing all people:
53
+
54
+ ```ruby
55
+ # List all people, auto pagination
56
+ # Note: Make sure to handle the possible rate limits error
57
+ people = Delighted::Person.list
58
+ begin
59
+ people.auto_paging_each do |person|
60
+ # Do something with person
61
+ end
62
+ rescue Delighted::RateLimitError => e
63
+ # Indicates how long to wait before making this request again
64
+ e.retry_after
65
+ retry
66
+ end
67
+
68
+ # For convenience, this method can use a sleep to automatically handle rate limits
69
+ people.auto_paging_each({ auto_handle_rate_limits: true }) do |person|
70
+ # Do something with person
71
+ end
72
+ ```
73
+
52
74
  Unsubscribing people:
53
75
 
54
76
  ```ruby
@@ -56,7 +78,7 @@ Unsubscribing people:
56
78
  Delighted::Unsubscribe.create(:person_email => "foo+test1@delighted.com")
57
79
  ```
58
80
 
59
- Listing people who have unsubscribed:
81
+ Listing people who have unsubscribed (auto pagination not supported):
60
82
 
61
83
  ```ruby
62
84
  # List all people who have unsubscribed, 20 per page, first 2 pages
@@ -64,7 +86,7 @@ survey_responses_page1 = Delighted::Unsubscribe.all
64
86
  survey_responses_page2 = Delighted::Unsubscribe.all(:page => 2)
65
87
  ```
66
88
 
67
- Listing people whose emails have bounced:
89
+ Listing people whose emails have bounced (auto pagination not supported):
68
90
 
69
91
  ```ruby
70
92
  # List all people whose emails have bounced, 20 per page, first 2 pages
@@ -9,9 +9,11 @@ require 'delighted/version'
9
9
  require 'delighted/utils'
10
10
  require 'delighted/json'
11
11
 
12
+ require 'delighted/list_resource'
12
13
  require 'delighted/enumerable_resource_collection'
13
14
  require 'delighted/resource'
14
15
  require 'delighted/operations/all'
16
+ require 'delighted/operations/list'
15
17
  require 'delighted/operations/create'
16
18
  require 'delighted/operations/retrieve'
17
19
  require 'delighted/operations/update'
@@ -2,6 +2,7 @@ module Delighted
2
2
  class Client
3
3
  DEFAULT_API_BASE_URL = "https://api.delightedapp.com/v1"
4
4
  DEFAULT_HTTP_ADAPTER = HTTPAdapter.new
5
+ DEFAULT_ACCEPT_HEADER = "application/json"
5
6
 
6
7
  def initialize(opts = {})
7
8
  @api_key = opts[:api_key] or raise ArgumentError, "You must provide an API key by setting Delighted.api_key = '123abc' or passing { :api_key => '123abc' } when instantiating Delighted::Client.new"
@@ -10,13 +11,21 @@ module Delighted
10
11
  end
11
12
 
12
13
  def get_json(path, params = {})
13
- headers = default_headers.dup.merge('Accept' => 'application/json')
14
+ request_get(path, {params: params})[:json]
15
+ end
14
16
 
15
- uri = URI.parse(File.join(@api_base_url, path))
17
+ def request_get(path, opts = {})
18
+ accept_header = opts.fetch(:accept_header, DEFAULT_ACCEPT_HEADER)
19
+ params = opts.fetch(:params, {})
20
+
21
+ headers = default_headers.dup.merge('Accept' => accept_header)
22
+
23
+ path = File.join(@api_base_url, path) unless is_full_url(path)
24
+ uri = URI.parse(path)
16
25
  uri.query = Utils.to_query(params) unless params.empty?
17
26
 
18
27
  response = @http_adapter.request(:get, uri, headers)
19
- handle_json_response(response)
28
+ { json: handle_json_response(response), response: response }
20
29
  end
21
30
 
22
31
  def post_json(path, params = {})
@@ -76,5 +85,9 @@ module Delighted
76
85
  'User-Agent' => "Delighted RubyGem #{Delighted::VERSION}"
77
86
  }.freeze
78
87
  end
88
+
89
+ def is_full_url(url)
90
+ !!(URI::regexp(%w(http https)) =~ url)
91
+ end
79
92
  end
80
93
  end
@@ -38,4 +38,8 @@ module Delighted
38
38
  class ServiceUnavailableError < Error
39
39
  # 503, maintenance or overloaded
40
40
  end
41
+
42
+ class PaginationError < StandardError
43
+ # e.g. pagination completed
44
+ end
41
45
  end
@@ -12,6 +12,11 @@ module Delighted
12
12
  get_header_value("content-type")
13
13
  end
14
14
 
15
+ def next_link
16
+ link_header = get_header_value("link")
17
+ parse_link_header(link_header)[:next]
18
+ end
19
+
15
20
  def retry_after
16
21
  if value = get_header_value("retry-after")
17
22
  value.to_i
@@ -37,5 +42,22 @@ module Delighted
37
42
  end
38
43
  end
39
44
  end
45
+
46
+ def parse_link_header(header_value)
47
+ links = {}
48
+ parts = Array(header_value)
49
+ .map { |v| v.split(",") }
50
+ .flatten
51
+ .map(&:strip)
52
+ # Parse each part into a named link
53
+ parts.each do |part|
54
+ section = part.split(';')
55
+ next if section.length < 2
56
+ url = section[0][/<(.*)>/,1]
57
+ name = section[1][/rel="?([^"]*)"?/,1].to_sym
58
+ links[name] = url
59
+ end
60
+ links
61
+ end
40
62
  end
41
63
  end
@@ -0,0 +1,45 @@
1
+ module Delighted
2
+ class ListResource
3
+ def initialize(klass, path, opts, client)
4
+ @class = klass
5
+ @path = path
6
+ @opts = opts
7
+ @client = client
8
+ @iteration_count = 0
9
+ @done = false
10
+ end
11
+
12
+ def auto_paging_each(opts = {})
13
+ raise PaginationError, "pagination completed" if @done
14
+
15
+ auto_handle_rate_limits = opts.fetch(:auto_handle_rate_limits, false)
16
+ loop do
17
+ begin
18
+ # Get next (or first) page
19
+ if @iteration_count == 0
20
+ data = @client.request_get(@path, { params: @opts })
21
+ else
22
+ data = @client.request_get(@next_link)
23
+ end
24
+ rescue Delighted::RateLimitedError => e
25
+ if auto_handle_rate_limits
26
+ sleep e.retry_after
27
+ retry
28
+ else
29
+ raise
30
+ end
31
+ end
32
+
33
+ @iteration_count += 1
34
+ @next_link = data[:response].next_link
35
+
36
+ data[:json].map do |attributes|
37
+ yield @class.new(attributes)
38
+ end
39
+
40
+ break if @next_link.nil?
41
+ end
42
+ @done = true
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ module Delighted
2
+ module Operations
3
+ module List
4
+ def self.included(klass)
5
+ klass.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def list(opts = {}, client = Delighted.shared_client)
10
+ ListResource.new(self, path, Utils.serialize_values(opts), client)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -4,5 +4,6 @@ module Delighted
4
4
 
5
5
  include Operations::Create
6
6
  include Operations::Delete
7
+ include Operations::List
7
8
  end
8
9
  end
@@ -1,3 +1,3 @@
1
1
  module Delighted
2
- VERSION = "1.8.0"
2
+ VERSION = "2.0.0rc1"
3
3
  end
@@ -38,6 +38,129 @@ class Delighted::MetricsTest < Delighted::TestCase
38
38
  end
39
39
 
40
40
  class Delighted::PeopleTest < Delighted::TestCase
41
+ def test_listing_people_auto_paginate
42
+ uri = URI.parse("https://api.delightedapp.com/v1/people")
43
+ uri_next = URI.parse("https://api.delightedapp.com/v1/people.json?page_info=123456789")
44
+ headers = { "Authorization" => @auth_header, "Accept" => "application/json", "User-Agent" => "Delighted RubyGem #{Delighted::VERSION}" }
45
+
46
+ # First request mock
47
+ example_person1 = {:person_id => "4945", :email => "foo@example.com", :name => "Gold"}
48
+ example_person2 = {:person_id => "4946", :email => "foo+2@example.com", :name => "Silver"}
49
+ response = Delighted::HTTPResponse.new(200, {"Link" => "<#{uri_next}>; rel=\"next\""}, Delighted::JSON.dump([example_person1,example_person2]))
50
+ mock_http_adapter.expects(:request).with(:get, uri, headers).once.returns(response)
51
+
52
+ # Next request mock
53
+ example_person_next = {:person_id => "4947", :email => "foo+3@example.com", :name => "Bronze"}
54
+ response = Delighted::HTTPResponse.new(200, {}, Delighted::JSON.dump([example_person_next]))
55
+ mock_http_adapter.expects(:request).with(:get, uri_next, headers).once.returns(response)
56
+
57
+ persons_all = []
58
+ Delighted::Person.list.auto_paging_each do |p|
59
+ persons_all << p
60
+ end
61
+
62
+ assert_equal 3, persons_all.size
63
+
64
+ first_person = persons_all[0]
65
+ assert_kind_of Delighted::Person, first_person
66
+ assert_equal "Gold", first_person.name
67
+ assert_equal example_person1, first_person.to_hash
68
+ second_person = persons_all[1]
69
+ assert_kind_of Delighted::Person, second_person
70
+ assert_equal "Silver", second_person.name
71
+ assert_equal example_person2, second_person.to_hash
72
+ third_person = persons_all[2]
73
+ assert_kind_of Delighted::Person, third_person
74
+ assert_equal "Bronze", third_person.name
75
+ assert_equal example_person_next, third_person.to_hash
76
+ end
77
+
78
+ def test_listing_people_rate_limited
79
+ uri = URI.parse("https://api.delightedapp.com/v1/people")
80
+ uri_next = URI.parse("https://api.delightedapp.com/v1/people.json?page_info=123456789")
81
+ headers = { "Authorization" => @auth_header, "Accept" => "application/json", "User-Agent" => "Delighted RubyGem #{Delighted::VERSION}" }
82
+
83
+ # First request mock
84
+ example_person1 = {:person_id => "4945", :email => "foo@example.com", :name => "Gold"}
85
+ response = Delighted::HTTPResponse.new(200, {"Link" => "<#{uri_next}>; rel=\"next\""}, Delighted::JSON.dump([example_person1]))
86
+ mock_http_adapter.expects(:request).with(:get, uri, headers).once.returns(response)
87
+
88
+ # Next rate limited request mock
89
+ response = Delighted::HTTPResponse.new(429, { "Retry-After" => "10" }, {})
90
+ mock_http_adapter.expects(:request).with(:get, uri_next, headers).once.returns(response)
91
+
92
+ persons_all = []
93
+ exception = assert_raises Delighted::RateLimitedError do
94
+ Delighted::Person.list.auto_paging_each({ :auto_handle_rate_limits => false }) do |p|
95
+ persons_all << p
96
+ end
97
+ end
98
+
99
+ assert_equal 10, exception.retry_after
100
+
101
+ assert_equal 1, persons_all.size
102
+ first_person = persons_all[0]
103
+ assert_kind_of Delighted::Person, first_person
104
+ assert_equal "Gold", first_person.name
105
+ assert_equal example_person1, first_person.to_hash
106
+ end
107
+
108
+ def test_listing_people_auto_handle_rate_limits
109
+ uri = URI.parse("https://api.delightedapp.com/v1/people")
110
+ uri_next = URI.parse("https://api.delightedapp.com/v1/people.json?page_info=123456789")
111
+ headers = { "Authorization" => @auth_header, "Accept" => "application/json", "User-Agent" => "Delighted RubyGem #{Delighted::VERSION}" }
112
+
113
+ # First request mock
114
+ example_person1 = {:person_id => "4945", :email => "foo@example.com", :name => "Gold"}
115
+ response = Delighted::HTTPResponse.new(200, {"Link" => "<#{uri_next}>; rel=\"next\""}, Delighted::JSON.dump([example_person1]))
116
+ mock_http_adapter.expects(:request).with(:get, uri, headers).once.returns(response)
117
+
118
+ # Next rate limited request mock, then accepted request
119
+ response_rate_limited = Delighted::HTTPResponse.new(429, { "Retry-After" => "3" }, {})
120
+ example_person_next = {:person_id => "4947", :email => "foo+next@example.com", :name => "Silver"}
121
+ response_ok = Delighted::HTTPResponse.new(200, {}, Delighted::JSON.dump([example_person_next]))
122
+ mock_http_adapter.expects(:request).with(:get, uri_next, headers).twice.returns(response_rate_limited, response_ok)
123
+
124
+ persons_all = []
125
+ people = Delighted::Person.list
126
+ people.expects(:sleep).with(3)
127
+ people.auto_paging_each({ :auto_handle_rate_limits => true }) do |p|
128
+ persons_all << p
129
+ end
130
+
131
+ assert_equal 2, persons_all.size
132
+ first_person = persons_all[0]
133
+ assert_kind_of Delighted::Person, first_person
134
+ assert_equal "Gold", first_person.name
135
+ assert_equal example_person1, first_person.to_hash
136
+ next_person = persons_all[1]
137
+ assert_kind_of Delighted::Person, next_person
138
+ assert_equal "Silver", next_person.name
139
+ assert_equal example_person_next, next_person.to_hash
140
+ end
141
+
142
+ def test_listing_people_auto_paginate_second_call
143
+ uri = URI.parse("https://api.delightedapp.com/v1/people")
144
+ headers = { "Authorization" => @auth_header, "Accept" => "application/json", "User-Agent" => "Delighted RubyGem #{Delighted::VERSION}" }
145
+
146
+ # First request mock
147
+ example_person1 = {:person_id => "4945", :email => "foo@example.com", :name => "Gold"}
148
+ response = Delighted::HTTPResponse.new(200, {}, Delighted::JSON.dump([example_person1]))
149
+ mock_http_adapter.expects(:request).with(:get, uri, headers).once.returns(response)
150
+
151
+ persons_all = []
152
+ people = Delighted::Person.list
153
+ people.auto_paging_each do |p|
154
+ persons_all << p
155
+ end
156
+
157
+ assert_equal 1, persons_all.size
158
+
159
+ assert_raises Delighted::PaginationError do
160
+ people.auto_paging_each
161
+ end
162
+ end
163
+
41
164
  def test_creating_or_updating_a_person
42
165
  uri = URI.parse("https://api.delightedapp.com/v1/people")
43
166
  headers = { 'Authorization' => @auth_header, "Accept" => "application/json", 'Content-Type' => 'application/json', 'User-Agent' => "Delighted RubyGem #{Delighted::VERSION}" }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delighted
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 2.0.0rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Dodwell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-22 00:00:00.000000000 Z
11
+ date: 2020-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -88,9 +88,11 @@ files:
88
88
  - lib/delighted/http_adapter.rb
89
89
  - lib/delighted/http_response.rb
90
90
  - lib/delighted/json.rb
91
+ - lib/delighted/list_resource.rb
91
92
  - lib/delighted/operations/all.rb
92
93
  - lib/delighted/operations/create.rb
93
94
  - lib/delighted/operations/delete.rb
95
+ - lib/delighted/operations/list.rb
94
96
  - lib/delighted/operations/retrieve.rb
95
97
  - lib/delighted/operations/update.rb
96
98
  - lib/delighted/resource.rb
@@ -120,12 +122,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
120
122
  version: '0'
121
123
  required_rubygems_version: !ruby/object:Gem::Requirement
122
124
  requirements:
123
- - - ">="
125
+ - - ">"
124
126
  - !ruby/object:Gem::Version
125
- version: '0'
127
+ version: 1.3.1
126
128
  requirements: []
127
129
  rubyforge_project:
128
- rubygems_version: 2.7.6
130
+ rubygems_version: 2.7.6.2
129
131
  signing_key:
130
132
  specification_version: 4
131
133
  summary: Delighted is the fastest and easiest way to gather actionable feedback from