Skip to content
This repository

Include GitHub events in the new Basecamp timeline #303

Merged
merged 2 commits into from almost 2 years ago

3 participants

Jeremy Kemper joshua vogelstein risk danger olson
Jeremy Kemper

Rather than spamming tons of Basecamp messages, this adds GitHub events directly to a project's timeline.

Using HTTP Basic until it's easier to grab an OAuth 2 token.

Renames the old Basecamp hook to Basecamp Classic, but keeps the same internal names for back compat.

added some commits April 24, 2012
Jeremy Kemper Include GitHub on the new Basecamp timeline.
Rename the old Basecamp hook to Basecamp Classic, but keep the internal
name for back-compat.
fddd20c
Jeremy Kemper Show 'via GitHub' on Basecamp events a1fe6ac
risk danger olson technoweenie merged commit 5851233 into from April 27, 2012
risk danger olson technoweenie closed this April 27, 2012
Jeremy Kemper

We'd love to do OAuth 2 here -- having to give up login creds breaks our own rules :bomb:

Barring first-class OAuth 2 support for service hook setup... would it be possible for the hook to get an access token, store it, and wipe the credentials, on the first use?

joshua vogelstein
jovo commented April 28, 2012

i assume "email_address" is just "user account"?

risk danger olson
Owner

Not really, hooks can't update themselves. It's an interesting idea though. I think I'd almost rather just have first-class OAuth 2 support in the admin UI. I'll keep it in mind though.

Jeremy Kemper

@jovo yeah, we're moving to emails as logins -- use your username there

@technoweenie builtin oauth2 would rock

risk danger olson
Owner

@jeremy: So, are you cool with the current feature set right now? Not sure we plan to add native OAuth 2 support any time soon. My next big thing for Hooks is making them much more reliable, and providing more informative errors.

I tell ya what, we can trade native OAuth 2 support for a Stars API :)

Jeremy Kemper

@technoweenie deal! We mark starred messages, but no API specifically for stars...

In the meantime, thinking about registering a GitHub client internally and adding a hidden page to Basecamp that issues an access token. Then the service hook would have a notice to go hit that URL and copy/paste the token over. Is it possible to change an existing hook like that once it's already launched, or would that need to be a new hook?

risk danger olson
Owner

That might work. Travis-CI uses the API to set hooks up. However, it requires the 'repo' scope. We really need more granular scopes for OAuth.

joshua vogelstein
jovo commented May 15, 2012

@jeremy - doesn't seem to work for me. any suggestions?

joshua vogelstein
jovo commented May 16, 2012

ah, i see it. in timeline only though. is that right?

Jeremy Kemper
jeremy commented May 16, 2012

@jovo That's right!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 2 unique commits by 1 author.

Apr 24, 2012
Jeremy Kemper Include GitHub on the new Basecamp timeline.
Rename the old Basecamp hook to Basecamp Classic, but keep the internal
name for back-compat.
fddd20c
Apr 26, 2012
Jeremy Kemper Show 'via GitHub' on Basecamp events a1fe6ac
This page is out of date. Refresh to see the latest.
18  docs/basecamp
@@ -4,24 +4,18 @@ Basecamp
4 4
 Install Notes
5 5
 -------------
6 6
 
7  
-  1. url should be your Basecamp url
8  
-  2. username should be the username or API token of the user that you want to use to post messages into your Basecamp - you can setup a user just for this purpose
9  
-  3. password should be the password of the user that you want to use to post the messages.  If username is an API token, set password to 'x'.
10  
-  4. project should be the name of the project that you want to post the message into (not the id)
11  
-  5. category should be the name of the category that you want to post the message using (not the id)
12  
-  6. ssl should be enabled for accounts that need SSL.
  7
+  1. project_url is the URL of your Basecamp project: https://basecamp.com/1234/projects/5678
  8
+  2. email_address is the email you sign in to Basecamp with. This person must have access to the project. To add events on behalf of other people, make the person an admin on the project.
  9
+  3. password is the password you sign in to Basecamp with.
13 10
 
14 11
 
15 12
 Developer Notes
16 13
 ---------------
17 14
 
18 15
 data
19  
-  - url
20  
-  - username
  16
+  - project_url
  17
+  - email_address
21 18
   - password
22  
-  - project
23  
-  - category
24  
-  - ssl
25 19
 
26 20
 payload
27  
-  - refer to docs/github_payload
  21
+  - refer to docs/github_payload
27  docs/basecamp_classic
... ...
@@ -0,0 +1,27 @@
  1
+Basecamp Classic
  2
+================
  3
+
  4
+Install Notes
  5
+-------------
  6
+
  7
+  1. url should be your Basecamp Classic url
  8
+  2. username should be the username or API token of the user that you want to use to post messages into your Basecamp - you can setup a user just for this purpose
  9
+  3. password should be the password of the user that you want to use to post the messages.  If username is an API token, set password to 'x'.
  10
+  4. project should be the name of the project that you want to post the message into (not the id)
  11
+  5. category should be the name of the category that you want to post the message using (not the id)
  12
+  6. ssl should be enabled for accounts that need SSL.
  13
+
  14
+
  15
+Developer Notes
  16
+---------------
  17
+
  18
+data
  19
+  - url
  20
+  - username
  21
+  - password
  22
+  - project
  23
+  - category
  24
+  - ssl
  25
+
  26
+payload
  27
+  - refer to docs/github_payload
170  services/basecamp.rb
... ...
@@ -1,143 +1,71 @@
1 1
 class Service::Basecamp < Service
2  
-  string   :url, :project, :category, :username
3  
-  password :password
4  
-  boolean  :ssl
  2
+  string          :project_url, :email_address
  3
+  password        :password
  4
+  white_list      :project_url, :email_address
  5
+  default_events  :push, :pull_request, :issues
5 6
 
6  
-  white_list :url, :project, :category, :username
7 7
 
8  
-  def receive_push
9  
-    raise_config_error "Invalid basecamp domain" if basecamp_domain.nil?
10  
-
11  
-    repository      = payload['repository']['name']
12  
-    name_with_owner = File.join(payload['repository']['owner']['name'], repository)
13  
-    branch          = ref_name
14  
-
15  
-    commits = payload['commits'].reject { |commit| commit['message'].to_s.strip == '' }
16  
-    return if commits.empty?
17  
-
18  
-    ::Basecamp.establish_connection! basecamp_domain,
19  
-      data['username'], data['password'], data['ssl'].to_i == 1
20  
-
21  
-    commits.each do |commit|
22  
-      gitsha        = commit['id']
23  
-      short_git_sha = gitsha[0..5]
24  
-      timestamp     = Date.parse(commit['timestamp'])
25  
-
26  
-      added         = commit['added'].map    { |f| ['A', f] }
27  
-      removed       = commit['removed'].map  { |f| ['R', f] }
28  
-      modified      = commit['modified'].map { |f| ['M', f] }
29  
-      changed_paths = (added + removed + modified).sort_by { |(char, file)| file }
30  
-      changed_paths = changed_paths.collect { |entry| entry * ' ' }.join("\n  ")
31  
-
32  
-      # Shorten the elements of the subject
33  
-      commit_title = commit['message'][/^([^\n]+)/, 1]
34  
-      if commit_title.length > 50
35  
-        commit_title = commit_title.slice(0,50) << '...'
36  
-      end
37  
-
38  
-      title = "Commit on #{name_with_owner}: #{short_git_sha}: #{commit_title}"
39  
-
40  
-      body = <<-EOH
41  
-*Author:* #{commit['author']['name']} <#{commit['author']['email']}>
42  
-*Commit:* <a href="#{commit['url']}">#{gitsha}</a>
43  
-*Date:*   #{timestamp} (#{timestamp.strftime('%a, %d %b %Y')})
44  
-*Branch:* #{branch}
45  
-*Home:*   #{payload['repository']['url']}
46  
-
47  
-h2. Log Message
48  
-
49  
-<pre>#{commit['message']}</pre>
50  
-EOH
51  
-
52  
-      if changed_paths.size > 0
53  
-        body << <<-EOH
54  
-
55  
-h2. Changed paths
56  
-
57  
-<pre>  #{changed_paths}</pre>
58  
-EOH
59  
-      end
  8
+  def hook_name
  9
+    'bcx'
  10
+  end
60 11
 
61  
-      post_message :title => title, :body => body
62  
-    end
  12
+  def receive_push
  13
+    commit = payload['commits'].last || {}
  14
+    author = commit['author'] || commit['committer'] || payload['pusher']
63 15
 
64  
-  rescue SocketError => boom
65  
-    if boom.to_s =~ /getaddrinfo: Name or service not known/
66  
-      raise_config_error "Invalid basecamp domain name"
67  
-    else
68  
-      raise
69  
-    end
70  
-  rescue ActiveResource::UnauthorizedAccess => boom
71  
-    raise_config_error "Unauthorized. Verify the project URL and credentials."
72  
-  rescue ActiveResource::ForbiddenAccess => boom
73  
-    raise_config_error boom.to_s
74  
-  rescue ActiveResource::Redirection => boom
75  
-    raise_config_error "Invalid project URL: #{boom}"
76  
-  rescue RuntimeError => boom
77  
-    if boom.to_s =~ /\((?:403|401|422)\)/
78  
-      raise_config_error "Invalid credentials: #{boom}"
79  
-    elsif boom.to_s =~ /\((?:404|301)\)/
80  
-      raise_config_error "Invalid project URL: #{boom}"
81  
-    elsif boom.to_s == 'Unprocessable Entity (422)'
82  
-      # do nothing
83  
-    else
84  
-      raise
85  
-    end
  16
+    message = summary_message.sub("[#{repo_name}] #{pusher_name} ", '')
  17
+    create_event 'committed', message, summary_url, author['email']
86 18
   end
87 19
 
88  
-  attr_writer :basecamp
89  
-  attr_writer :project_id
90  
-  attr_writer :category_id
  20
+  def receive_pull_request
  21
+    base_ref = pull.base.label.split(':').last
  22
+    head_ref = pull.head.label.split(':').last
  23
+    head_ref = pull.head.label if head_ref == base_ref
91 24
 
92  
-  def basecamp_domain
93  
-    @basecamp_domain ||= Addressable::URI.parse(data['url'].to_s).host
94  
-  rescue Addressable::URI::InvalidURIError
  25
+    create_event "#{action} a pull request",
  26
+      "#{pull.title} (#{base_ref}..#{head_ref})",
  27
+      pull.html_url
95 28
   end
96 29
 
97  
-  def build_message(options = {})
98  
-    m = ::Basecamp::Message.new :project_id => project_id
99  
-    m.category_id = category_id
100  
-    options.each do |key, value|
101  
-      m.send "#{key}=", value
102  
-    end
103  
-    m
  30
+  def receive_issues
  31
+    create_event "#{action} an issue", issue.title, issue.html_url
104 32
   end
105 33
 
106  
-  def post_message(options = {})
107  
-    build_message(options).save
108  
-  end
109 34
 
110  
-  def all_projects
111  
-    Array(::Basecamp::Project.all)
112  
-  end
  35
+  private
113 36
 
114  
-  def all_categories
115  
-    Array(::Basecamp::Category.post_categories(project_id))
  37
+  def create_event(action, message, url, author_email = nil)
  38
+    http_post_event :service => 'GitHub',
  39
+      :creator_email_address => author_email,
  40
+      :description => action,
  41
+      :title => message,
  42
+      :url => url
116 43
   end
117 44
 
118  
-  def project_id
119  
-    @project_id ||= begin
120  
-      name = data['project'].to_s
121  
-      name.downcase!
122  
-      projects = all_projects.select { |p| p.name.downcase == name }
123  
-      case projects.size
124  
-      when 1 then projects.first.id
125  
-      when 0 then raise_config_error("Invalid Project: #{name.downcase}")
126  
-      else raise_config_error("Multiple projects named: #{name.downcase}")
127  
-      end
  45
+  def http_post_event(params)
  46
+    http.basic_auth data['email_address'], data['password']
  47
+    http.headers['User-Agent']    = 'GitHub service hook'
  48
+    http.headers['Content-Type']  = 'application/json'
  49
+    http.headers['Accept']        = 'application/json'
  50
+
  51
+    response = http_post(events_api_url, params.to_json)
  52
+
  53
+    case response.status
  54
+    when 401; raise_config_error "Invalid email + password: #{response.body.inspect}"
  55
+    when 403; raise_config_error "No access to project: #{response.body.inspect}"
  56
+    when 404; raise_config_error "No such project: #{response.body.inspect}"
  57
+    when 422; raise_config_error "Validation error: #{response.body.inspect}"
128 58
     end
129 59
   end
130 60
 
131  
-  def category_id
132  
-    @category_id ||= begin
133  
-      name = data['category'].to_s
134  
-      name.downcase!
135  
-      categories = all_categories.select { |c| c.name.downcase == name }
136  
-      case categories.size
137  
-      when 1 then categories.first.id
138  
-      when 0 then raise_config_error("Invalid Category: #{name.downcase}")
139  
-      else raise_config_error("Multiple categories named: #{name.downcase}")
140  
-      end
  61
+  EVENTS_API_URL = 'https://basecamp.com:443/%d/api/v1/projects/%d/events.json'
  62
+  def events_api_url
  63
+    if data['project_url'] =~ %r{^https://basecamp\.com/(\d+)/projects/(\d+)}
  64
+      EVENTS_API_URL % [$1, $2]
  65
+    elsif data['project_url'] =~ /basecamphq\.com/
  66
+      raise_config_error "That's a URL for a Basecamp Classic project, not the new Basecamp. Check out the Basecamp Classic service hook instead!"
  67
+    else
  68
+      raise_config_error "That's not a URL to a Basecamp project! Navigate to the Basecamp project you'd like to post to and note the URL. It should look something like: https://basecamp.com/123456/projects/7890123 -- paste that URL here."
141 69
     end
142 70
   end
143 71
 end
147  services/basecamp_classic.rb
... ...
@@ -0,0 +1,147 @@
  1
+class Service::BasecampClassic < Service
  2
+  string      :url, :project, :category, :username
  3
+  password    :password
  4
+  boolean     :ssl
  5
+  white_list  :url, :project, :category, :username
  6
+
  7
+
  8
+  def hook_name
  9
+    'basecamp'
  10
+  end
  11
+
  12
+  def receive_push
  13
+    raise_config_error "Invalid basecamp domain" if basecamp_domain.nil?
  14
+
  15
+    repository      = payload['repository']['name']
  16
+    name_with_owner = File.join(payload['repository']['owner']['name'], repository)
  17
+    branch          = ref_name
  18
+
  19
+    commits = payload['commits'].reject { |commit| commit['message'].to_s.strip == '' }
  20
+    return if commits.empty?
  21
+
  22
+    ::Basecamp.establish_connection! basecamp_domain,
  23
+      data['username'], data['password'], data['ssl'].to_i == 1
  24
+
  25
+    commits.each do |commit|
  26
+      gitsha        = commit['id']
  27
+      short_git_sha = gitsha[0..5]
  28
+      timestamp     = Date.parse(commit['timestamp'])
  29
+
  30
+      added         = commit['added'].map    { |f| ['A', f] }
  31
+      removed       = commit['removed'].map  { |f| ['R', f] }
  32
+      modified      = commit['modified'].map { |f| ['M', f] }
  33
+      changed_paths = (added + removed + modified).sort_by { |(char, file)| file }
  34
+      changed_paths = changed_paths.collect { |entry| entry * ' ' }.join("\n  ")
  35
+
  36
+      # Shorten the elements of the subject
  37
+      commit_title = commit['message'][/^([^\n]+)/, 1]
  38
+      if commit_title.length > 50
  39
+        commit_title = commit_title.slice(0,50) << '...'
  40
+      end
  41
+
  42
+      title = "Commit on #{name_with_owner}: #{short_git_sha}: #{commit_title}"
  43
+
  44
+      body = <<-EOH
  45
+*Author:* #{commit['author']['name']} <#{commit['author']['email']}>
  46
+*Commit:* <a href="#{commit['url']}">#{gitsha}</a>
  47
+*Date:*   #{timestamp} (#{timestamp.strftime('%a, %d %b %Y')})
  48
+*Branch:* #{branch}
  49
+*Home:*   #{payload['repository']['url']}
  50
+
  51
+h2. Log Message
  52
+
  53
+<pre>#{commit['message']}</pre>
  54
+EOH
  55
+
  56
+      if changed_paths.size > 0
  57
+        body << <<-EOH
  58
+
  59
+h2. Changed paths
  60
+
  61
+<pre>  #{changed_paths}</pre>
  62
+EOH
  63
+      end
  64
+
  65
+      post_message :title => title, :body => body
  66
+    end
  67
+
  68
+  rescue SocketError => boom
  69
+    if boom.to_s =~ /getaddrinfo: Name or service not known/
  70
+      raise_config_error "Invalid basecamp domain name"
  71
+    else
  72
+      raise
  73
+    end
  74
+  rescue ActiveResource::UnauthorizedAccess => boom
  75
+    raise_config_error "Unauthorized. Verify the project URL and credentials."
  76
+  rescue ActiveResource::ForbiddenAccess => boom
  77
+    raise_config_error boom.to_s
  78
+  rescue ActiveResource::Redirection => boom
  79
+    raise_config_error "Invalid project URL: #{boom}"
  80
+  rescue RuntimeError => boom
  81
+    if boom.to_s =~ /\((?:403|401|422)\)/
  82
+      raise_config_error "Invalid credentials: #{boom}"
  83
+    elsif boom.to_s =~ /\((?:404|301)\)/
  84
+      raise_config_error "Invalid project URL: #{boom}"
  85
+    elsif boom.to_s == 'Unprocessable Entity (422)'
  86
+      # do nothing
  87
+    else
  88
+      raise
  89
+    end
  90
+  end
  91
+
  92
+  attr_writer :basecamp
  93
+  attr_writer :project_id
  94
+  attr_writer :category_id
  95
+
  96
+  def basecamp_domain
  97
+    @basecamp_domain ||= Addressable::URI.parse(data['url'].to_s).host
  98
+  rescue Addressable::URI::InvalidURIError
  99
+  end
  100
+
  101
+  def build_message(options = {})
  102
+    m = ::Basecamp::Message.new :project_id => project_id
  103
+    m.category_id = category_id
  104
+    options.each do |key, value|
  105
+      m.send "#{key}=", value
  106
+    end
  107
+    m
  108
+  end
  109
+
  110
+  def post_message(options = {})
  111
+    build_message(options).save
  112
+  end
  113
+
  114
+  def all_projects
  115
+    Array(::Basecamp::Project.all)
  116
+  end
  117
+
  118
+  def all_categories
  119
+    Array(::Basecamp::Category.post_categories(project_id))
  120
+  end
  121
+
  122
+  def project_id
  123
+    @project_id ||= begin
  124
+      name = data['project'].to_s
  125
+      name.downcase!
  126
+      projects = all_projects.select { |p| p.name.downcase == name }
  127
+      case projects.size
  128
+      when 1 then projects.first.id
  129
+      when 0 then raise_config_error("Invalid Project: #{name.downcase}")
  130
+      else raise_config_error("Multiple projects named: #{name.downcase}")
  131
+      end
  132
+    end
  133
+  end
  134
+
  135
+  def category_id
  136
+    @category_id ||= begin
  137
+      name = data['category'].to_s
  138
+      name.downcase!
  139
+      categories = all_categories.select { |c| c.name.downcase == name }
  140
+      case categories.size
  141
+      when 1 then categories.first.id
  142
+      when 0 then raise_config_error("Invalid Category: #{name.downcase}")
  143
+      else raise_config_error("Multiple categories named: #{name.downcase}")
  144
+      end
  145
+    end
  146
+  end
  147
+end
30  test/basecamp_classic_test.rb
... ...
@@ -0,0 +1,30 @@
  1
+require File.expand_path('../helper', __FILE__)
  2
+
  3
+class BasecampClassicTest < Service::TestCase
  4
+  def test_receives_push
  5
+    svc = service :push, {'url' => 'https://foo.com', 'username' => 'monkey', 'password' => 'abc'}, payload
  6
+    svc.receive
  7
+
  8
+    assert msg = svc.messages.shift
  9
+    assert_equal 2, msg.category_id
  10
+    assert msg.title.present?
  11
+    assert msg.body.present?
  12
+  end
  13
+
  14
+  def service(*args)
  15
+    svc = super Service::BasecampClassic, *args
  16
+
  17
+    svc.project_id  = 1
  18
+    svc.category_id = 2
  19
+
  20
+    def svc.messages
  21
+      @messages ||= []
  22
+    end
  23
+
  24
+    def svc.post_message(options = {})
  25
+      messages << build_message(options)
  26
+    end
  27
+
  28
+    svc
  29
+  end
  30
+end
91  test/basecamp_test.rb
... ...
@@ -1,30 +1,87 @@
1 1
 require File.expand_path('../helper', __FILE__)
2 2
 
3 3
 class BasecampTest < Service::TestCase
4  
-  def test_receives_push
5  
-    svc = service :push, {'url' => 'https://foo.com', 'username' => 'monkey', 'password' => 'abc'}, payload
6  
-    svc.receive
7  
-
8  
-    assert msg = svc.messages.shift
9  
-    assert_equal 2, msg.category_id
10  
-    assert msg.title.present?
11  
-    assert msg.body.present?
  4
+  def setup
  5
+    @stubs = Faraday::Adapter::Test::Stubs.new
  6
+
  7
+    @options = {
  8
+      'project_url'   => 'https://basecamp.com/123/projects/456',
  9
+      'email_address' => 'a@b.com',
  10
+      'password'      => 'secret' }
12 11
   end
13 12
 
14  
-  def service(*args)
15  
-    svc = super Service::Basecamp, *args
  13
+  def test_push
  14
+    @stubs.post '/123/api/v1/projects/456/events.json' do |env|
  15
+      assert_equal 'https', env[:url].scheme
  16
+      assert_equal 'basecamp.com', env[:url].host
  17
+
  18
+      assert_equal 'Basic YUBiLmNvbTpzZWNyZXQ=', env[:request_headers]['Authorization']
16 19
 
17  
-    svc.project_id  = 1
18  
-    svc.category_id = 2
  20
+      assert_match 'GitHub', env[:request_headers]['User-Agent']
  21
+      assert_equal 'application/json', env[:request_headers]['Content-Type']
  22
+      assert_equal 'application/json', env[:request_headers]['Accept']
19 23
 
20  
-    def svc.messages
21  
-      @messages ||= []
  24
+      expected = {
  25
+        'service' => 'github',
  26
+        'creator_email_address' => 'tom@mojombo.com',
  27
+        'description' => 'committed',
  28
+        'title' => 'pushed 3 new commits to master',
  29
+        'url' => 'http://github.com/mojombo/grit/compare/4c8124f...a47fd41' }
  30
+      assert_equal expected, JSON.parse(env[:body])
  31
+
  32
+      [200, {}, '']
22 33
     end
23 34
 
24  
-    def svc.post_message(options = {})
25  
-      messages << build_message(options)
  35
+    service(@options, payload).receive_push
  36
+  end
  37
+
  38
+  def test_pull
  39
+    @stubs.post '/123/api/v1/projects/456/events.json' do |env|
  40
+      expected = {
  41
+        'service' => 'github',
  42
+        'creator_email_address' => nil,
  43
+        'description' => 'opened a pull request',
  44
+        'title' => 'booya (master..feature)',
  45
+        'url' => 'html_url' }
  46
+      assert_equal expected, JSON.parse(env[:body])
  47
+
  48
+      [200, {}, '']
26 49
     end
27 50
 
28  
-    svc
  51
+    service(:pull_request, @options, pull_payload).receive_pull_request
  52
+  end
  53
+
  54
+  def test_issues
  55
+    @stubs.post '/123/api/v1/projects/456/events.json' do |env|
  56
+      expected = {
  57
+        'service' => 'github',
  58
+        'creator_email_address' => nil,
  59
+        'description' => 'opened an issue',
  60
+        'title' => 'booya',
  61
+        'url' => 'html_url' }
  62
+      assert_equal expected, JSON.parse(env[:body])
  63
+
  64
+      [200, {}, '']
  65
+    end
  66
+
  67
+    service(:issues, @options, issues_payload).receive_issues
  68
+  end
  69
+
  70
+  def service(*args)
  71
+    super Service::Basecamp, *args
  72
+  end
  73
+
  74
+  # No html_url in default payload
  75
+  def pull_payload
  76
+    super.tap do |payload|
  77
+      payload['pull_request']['html_url'] = 'html_url'
  78
+    end
  79
+  end
  80
+
  81
+  # No html_url in default payload
  82
+  def issues_payload
  83
+    super.tap do |payload|
  84
+      payload['issue']['html_url'] = 'html_url'
  85
+    end
29 86
   end
30 87
 end
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.