delighted 1.8.0 → 2.0.0rc1

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 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