sentry-api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 63d5a7432a7a86a1c8077fb4d702701632640a76
4
+ data.tar.gz: 7aee07004fab408b8a662e3ed394090408ecbdda
5
+ SHA512:
6
+ metadata.gz: ada67008ed2eaa102a87ff5b2e3bd370aaf57f408371261149984e8f4f220319c68c28180ca691958b2cba5911d67fa25366b1f8bce132b480fe7a9f1b9a2517
7
+ data.tar.gz: 31879008fa89a01e1601f19dc0fc0754b5677a5b726bfefcf745efa20815b7b53a946be315baf090c7d2661e0ebe5e9835e04ea1c9c3e741c4130fa96fb41de4
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea/
11
+ /test/
12
+ *.gem
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0
5
+ - 2.1
6
+ - 2.2
7
+ - 2.3.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sentry.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2015-2016 Thierry Xing <thierry.xing@gmail.com>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24
+ POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # Sentry Ruby API
2
+ [![Build Status](https://travis-ci.org/thierryxing/sentry-ruby-api.svg?branch=master)](https://travis-ci.org/thierryxing/sentry-ruby-api)
3
+ [![License](https://img.shields.io/badge/license-BSD-red.svg?style=flat)](https://github.com/thierryxing/sentry-ruby-api/blob/master/LICENSE.txt)
4
+
5
+ Sentry Ruby API is a Ruby wrapper for the [getsentry/sentry API](https://docs.sentry.io/hosted/api/).
6
+
7
+
8
+ ## Installation
9
+
10
+ add to a Gemfile:
11
+
12
+ ```ruby
13
+ gem 'sentry', :git => "git@github.com:thierryxing/sentry-ruby-api.git"
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ Configuration example:
19
+
20
+ ```ruby
21
+ SentryApi.configure do |config|
22
+ config.endpoint = 'http://example.com/api/0'
23
+ config.auth_token = 'your_auth_token'
24
+ config.default_org_slug = 'sentry-sc'
25
+ end
26
+ ```
27
+
28
+ (Note: If you are using getsentry.com's hosted service, your endpoint will be `https://app.getsentry.com/api/0`)
29
+
30
+ Usage examples:
31
+
32
+ ```ruby
33
+ # set an API endpoint
34
+ SentryApi.endpoint = 'http://example.com/api/0'
35
+ # => "http://example.com/api/0"
36
+
37
+ # set a user private token
38
+ SentryApi.auth_token = 'your_auth_token'
39
+ # => "your_auth_token"
40
+
41
+ # configure a proxy server
42
+ SentryApi.http_proxy('proxyhost', 8888)
43
+ # proxy server w/ basic auth
44
+ SentryApi.http_proxy('proxyhost', 8888, 'user', 'pass')
45
+
46
+ # list projects
47
+ SentryApi.projects
48
+
49
+ # initialize a new client
50
+ s = SentryApi.client(endpoint: 'https://api.example.com', auth_token: 'your_auth_token', default_org_slug: 'sentry-sc')
51
+
52
+ # a paginated response
53
+ projects = SentryApi.projects
54
+
55
+ # check existence of the next page
56
+ projects.has_next_page?
57
+
58
+ # retrieve the next page
59
+ projects.next_page
60
+
61
+ # iterate all projects
62
+ projects.auto_paginate do |project|
63
+ # do something
64
+ end
65
+
66
+ # retrieve all projects as an array
67
+ projects.auto_paginate
68
+ ```
69
+
70
+ ## Development
71
+ The basic framework had been finished, meanwhile the APIs of Organizations,Projects and Events had been added, more APIs will be added later. You are welcome to help me with it.
72
+
73
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
74
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
75
+ prompt that will allow you to experiment.
76
+
77
+ ## License
78
+
79
+ Released under the BSD 2-clause license. See LICENSE.txt for details.
80
+
81
+ ## Special Thank
82
+ Thanks to NARKOZ's [gitlab](https://github.com/NARKOZ/gitlab) ruby wrapper which really gives me a lot of inspiration.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "sentry-api"
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
data/bin/setup ADDED
@@ -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
data/lib/sentry-api.rb ADDED
@@ -0,0 +1,45 @@
1
+ require 'sentry-api/version'
2
+ require 'sentry-api/objectified_hash'
3
+ require 'sentry-api/configuration'
4
+ require 'sentry-api/error'
5
+ require 'sentry-api/page_links'
6
+ require 'sentry-api/paginated_response'
7
+ require 'sentry-api/request'
8
+ require 'sentry-api/api'
9
+ require 'sentry-api/client'
10
+
11
+ module SentryApi
12
+ extend Configuration
13
+
14
+ # Alias for Sentry::Client.new
15
+ #
16
+ # @return [Sentry::Client]
17
+ def self.client(options={})
18
+ SentryApi::Client.new(options)
19
+ end
20
+
21
+ # Delegate to Sentry::Client
22
+ def self.method_missing(method, *args, &block)
23
+ return super unless client.respond_to?(method)
24
+ client.send(method, *args, &block)
25
+ end
26
+
27
+ # Delegate to Sentry::Client
28
+ def respond_to_missing?(method_name, include_private = false)
29
+ client.respond_to?(method_name) || super
30
+ end
31
+
32
+ # Delegate to HTTParty.http_proxy
33
+ def self.http_proxy(address=nil, port=nil, username=nil, password=nil)
34
+ SentryApi::Request.http_proxy(address, port, username, password)
35
+ end
36
+
37
+ # Returns an unsorted array of available client methods.
38
+ #
39
+ # @return [Array<Symbol>]
40
+ def self.actions
41
+ hidden = /endpoint|auth_token|default_org_slug|get|post|put|delete|validate|set_request_defaults|httparty/
42
+ (SentryApi::Client.instance_methods - Object.methods).reject { |e| e[hidden] }
43
+ end
44
+
45
+ end
@@ -0,0 +1,17 @@
1
+ module SentryApi
2
+ # @private
3
+ class API < Request
4
+ # @private
5
+ attr_accessor(*Configuration::VALID_OPTIONS_KEYS)
6
+
7
+ # Creates a new API.
8
+ # @raise [Error:MissingCredentials]
9
+ def initialize(options={})
10
+ options = SentryApi.options.merge(options)
11
+ (Configuration::VALID_OPTIONS_KEYS).each do |key|
12
+ send("#{key}=", options[key]) if options[key]
13
+ end
14
+ set_request_defaults
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ module SentryApi
2
+ # Wrapper for the Sentry REST API.
3
+ class Client < API
4
+ Dir[File.expand_path('../client/*.rb', __FILE__)].each { |f| require f }
5
+
6
+ include Organizations
7
+ include Projects
8
+ include Events
9
+ end
10
+ end
@@ -0,0 +1,91 @@
1
+ class SentryApi::Client
2
+
3
+ module Events
4
+
5
+ # Retrieve an Issue
6
+ #
7
+ # @example
8
+ # SentryApi.issue('120732258')
9
+ #
10
+ # @param issue_id [String] the ID of the issue to retrieve.
11
+ # @return [SentryApi::ObjectifiedHash]
12
+ def issue(issue_id)
13
+ get("/issues/#{issue_id}/")
14
+ end
15
+
16
+ # List an Issue’s Events
17
+ #
18
+ # @example
19
+ # SentryApi.issue_events('120732258')
20
+ #
21
+ # @param issue_id [String] the ID of the issue to retrieve.
22
+ # @return [Array<SentryApi::ObjectifiedHash>]
23
+ def issue_events(issue_id)
24
+ get("/issues/#{issue_id}/events/")
25
+ end
26
+
27
+ # List an Issue’s Hashes
28
+ #
29
+ # @example
30
+ # SentryApi.issues_hashes('120732258')
31
+ #
32
+ # @param issue_id [String] the ID of the issue to retrieve.
33
+ # @return [Array<SentryApi::ObjectifiedHash>]
34
+ def issue_hashes(issue_id)
35
+ get("/issues/#{issue_id}/hashes/")
36
+ end
37
+
38
+ # Removes an individual issue.
39
+ #
40
+ # @example
41
+ # SentryApi.remove_issue('120732258')
42
+ #
43
+ # @param issue_id [String] the ID of the issue to retrieve.
44
+ def remove_issue(issue_id)
45
+ delete("/issues/#{issue_id}/")
46
+ end
47
+
48
+ # Update an individual issue.
49
+ #
50
+ # @example
51
+ # SentryApi.update_issue('120732258')
52
+ # SentryApi.update_issue('120732258',{status:'resolved'})
53
+ # SentryApi.update_issue('120732258',{status:'resolved', assignedTo:'thierry.xing@gmail.com'})
54
+ #
55
+ # @param issue_id [String] the ID of the issue to retrieve.
56
+ # @param [Hash] options A customizable set of options.
57
+ # @option options [String] :status the new status for the groups. Valid values are "resolved", "unresolved" and "muted".
58
+ # @option options [String] :assignedTo the username of the user that should be assigned to this issue.
59
+ # @option options [Boolean] :hasSeen in case this API call is invoked with a user context this allows changing of the flag that indicates if the user has seen the event.
60
+ # @option options [Boolean] :isBookmarked in case this API call is invoked with a user context this allows changing of the bookmark flag.
61
+ # @option options [Boolean] :isSubscribed in case this API call is invoked with a user context this allows changing of the subscribed flag.
62
+ # @return <SentryApi::ObjectifiedHash>
63
+ def update_issue(issue_id, options={})
64
+ put("/issues/#{issue_id}/", body: options)
65
+ end
66
+
67
+ # Retrieves the details of the latest event.
68
+ #
69
+ # @example
70
+ # SentryApi.latest_event('120633628')
71
+ #
72
+ # @param issue_id [String] the ID of the issue to retrieve.
73
+ # @return [SentryApi::ObjectifiedHash]
74
+ def latest_event(issue_id)
75
+ get("/issues/#{issue_id}/events/latest/")
76
+ end
77
+
78
+ # Retrieves the details of the oldest event.
79
+ #
80
+ # @example
81
+ # SentryApi.oldest_event('120633628')
82
+ #
83
+ # @param issue_id [String] the ID of the issue to retrieve.
84
+ # @return [SentryApi::ObjectifiedHash]
85
+ def oldest_event(issue_id)
86
+ get("/issues/#{issue_id}/events/oldest/")
87
+ end
88
+
89
+ end
90
+
91
+ end
@@ -0,0 +1,76 @@
1
+ class SentryApi::Client
2
+
3
+ module Organizations
4
+ # List your Organizations.
5
+ #
6
+ # @example
7
+ # SentryApi.organizations
8
+ #
9
+ # @param member [Boolean] Restrict results to organizations which you have membership
10
+ # @return [Array<SentryApi::ObjectifiedHash>]
11
+ def organizations(member=false)
12
+ get("/organizations/", query: {member: member})
13
+ end
14
+
15
+ # List an Organization’s Projects
16
+ #
17
+ # @example
18
+ # SentryApi.organization_projects
19
+ # SentryApi.organization_projects('slug')
20
+ #
21
+ # @param organization_slug [String] the slug of the organization for which the projects should be listed.
22
+ # @return [Array<SentryApi::ObjectifiedHash>]
23
+ def organization_projects(organization_slug="")
24
+ organization_slug = @default_org_slug if organization_slug == ""
25
+ get("/organizations/#{organization_slug}/projects/")
26
+ end
27
+
28
+ # Retrieve an Organization
29
+ #
30
+ # @example
31
+ # SentryApi.organization
32
+ # SentryApi.organization('slug')
33
+ #
34
+ # @param organization_slug [String] the slug of the organization the team should be created for.
35
+ # @return [SentryApi::ObjectifiedHash]
36
+ def organization(organization_slug="")
37
+ organization_slug = @default_org_slug if organization_slug == ""
38
+ get("/organizations/#{organization_slug}/")
39
+ end
40
+
41
+ # Update an Organization
42
+ #
43
+ # @example
44
+ # SentryApi.update_organization('slug')
45
+ # SentryApi.update_organization('slug',{name:'new-name'})
46
+ # SentryApi.update_organization('slug',{name:'new-name', slug:'new-slug'})
47
+ #
48
+ # @param organization_slug [String] the slug of the organization the team should be created for.
49
+ # @param [Hash] options A customizable set of options.
50
+ # @option options [String] :name an optional new name for the organization.
51
+ # @option options [String] :slug an optional new slug for the organization. Needs to be available and unique.
52
+ # @return [SentryApi::ObjectifiedHash]
53
+ def update_organization(organization_slug, options={})
54
+ put("/organizations/#{organization_slug}/", body: options)
55
+ end
56
+
57
+ # Retrieve Event Counts for an Organization
58
+ #
59
+ # @example
60
+ # SentryApi.organization_stats('slug')
61
+ # SentryApi.organization_stats('slug', {stat:'received', since:'1472158800'})
62
+ #
63
+ # @param organization_slug [String] the slug of the organization for which the stats should be retrieved.
64
+ # @param [Hash] options A customizable set of options.
65
+ # @option options [String] :stat the name of the stat to query ("received", "rejected", "blacklisted")
66
+ # @option options [Timestamp] :since a timestamp to set the start of the query in seconds since UNIX epoch.
67
+ # @option options [Timestamp] :until a timestamp to set the end of the query in seconds since UNIX epoch.
68
+ # @option options [String] :resolution an explicit resolution to search for (eg: 10s). This should not be used unless you are familiar with Sentry’s internals as it’s restricted to pre-defined values.
69
+ # @return [Array<Array>]
70
+ def organization_stats(organization_slug, options={})
71
+ get("/organizations/#{organization_slug}/stats/", query: options)
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,185 @@
1
+ class SentryApi::Client
2
+
3
+ module Projects
4
+ # List your Projects
5
+ #
6
+ # @example
7
+ # SentryApi.projects
8
+ #
9
+ # @return [Array<SentryApi::ObjectifiedHash>]
10
+ def projects
11
+ get("/projects/")
12
+ end
13
+
14
+ # Retrieve a Project
15
+ #
16
+ # @example
17
+ # SentryApi.project('project-slug')
18
+ #
19
+ # @param project_slug [String] the slug of the project to retrieve.
20
+ # @param organization_slug [String] the slug of the organization the project belong to.
21
+ # @return [SentryApi::ObjectifiedHash]
22
+ def project(project_slug, organization_slug="")
23
+ organization_slug = @default_org_slug if organization_slug == ""
24
+ get("/projects/#{organization_slug}/#{project_slug}/")
25
+ end
26
+
27
+ # Update a Project
28
+ #
29
+ # @example
30
+ # SentryApi.update_project('project-slug', {name:'new-name', slug:'new-slug', is_bookmarked:false})
31
+ #
32
+ # @param project_slug [String] the slug of the project to retrieve.
33
+ # @param [Hash] options A customizable set of options.
34
+ # @option options [String] :name the new name for the project.
35
+ # @option options [String] :slug the new slug for the project.
36
+ # @option options [String] :isBookmarked in case this API call is invoked with a user context this allows changing of the bookmark flag.
37
+ # @option options [Hash] optional options to override in the project settings.
38
+ # @param organization_slug [String] the slug of the organization the project belong to.
39
+ # @return [SentryApi::ObjectifiedHash]
40
+ def update_project(project_slug, options={}, organization_slug="")
41
+ organization_slug = @default_org_slug if organization_slug == ""
42
+ put("/projects/#{organization_slug}/#{project_slug}/", body: options)
43
+ end
44
+
45
+ # Delete a Project.
46
+ #
47
+ # @example
48
+ # SentryApi.delete_project('project-slug')
49
+ #
50
+ # @param project_slug [String] the slug of the project to delete.
51
+ # @param organization_slug [String] the slug of the organization the project belong to.
52
+ def delete_project(project_slug, organization_slug="")
53
+ organization_slug = @default_org_slug if organization_slug == ""
54
+ delete("/projects/#{organization_slug}/#{project_slug}/")
55
+ end
56
+
57
+ # Retrieve Event Counts for an Project
58
+ #
59
+ # @example
60
+ # SentryApi.project_stats('slug')
61
+ # SentryApi.project_stats('slug', {stat:'received', since:'1472158800'})
62
+ #
63
+ # @param project_slug [String] the slug of the project.
64
+ # @param [Hash] options A customizable set of options.
65
+ # @option options [String] :stat the name of the stat to query ("received", "rejected", "blacklisted")
66
+ # @option options [Timestamp] :since a timestamp to set the start of the query in seconds since UNIX epoch.
67
+ # @option options [Timestamp] :until a timestamp to set the end of the query in seconds since UNIX epoch.
68
+ # @option options [String] :resolution an explicit resolution to search for (eg: 10s). This should not be used unless you are familiar with Sentry’s internals as it’s restricted to pre-defined values.
69
+ # @param organization_slug [String] the slug of the organization the project belong to.
70
+ # @return [Array<Array>]
71
+ def project_stats(project_slug, options={}, organization_slug="")
72
+ get("/projects/#{organization_slug}/#{project_slug}/stats/", query: options)
73
+ end
74
+
75
+ # List a Project’s DSym Files.
76
+ #
77
+ # @example
78
+ # SentryApi.project_dsym_files('project-slug')
79
+ #
80
+ # @param organization_slug [String] the slug of the organization.
81
+ # @param project_slug [String] the slug of the project to list the dsym files of.
82
+ # @return [Array<SentryApi::ObjectifiedHash>]
83
+ def project_dsym_files(project_slug, organization_slug="")
84
+ organization_slug = @default_org_slug if organization_slug == ""
85
+ get("/projects/#{organization_slug}/#{project_slug}/files/dsyms/")
86
+ end
87
+
88
+ # List a Project’s Client Keys.
89
+ #
90
+ # @example
91
+ # SentryApi.client_keys('project-slug')
92
+ #
93
+ # @param project_slug [String] the slug of the project the client keys belong to.
94
+ # @param organization_slug [String] the slug of the organization the client keys belong to.
95
+ # @return [Array<SentryApi::ObjectifiedHash>]
96
+ def client_keys(project_slug, organization_slug="")
97
+ organization_slug = @default_org_slug if organization_slug == ""
98
+ get("/projects/#{organization_slug}/#{project_slug}/keys/")
99
+ end
100
+
101
+ # Create a new Client Key.
102
+ #
103
+ # @example
104
+ # SentryApi.create_client_key('project-slug','new-name')
105
+ #
106
+ # @param project_slug [String] the slug of the project the client keys belong to.
107
+ # @param [Hash] options A customizable set of options.
108
+ # @option options [String] :name the name for the new key.
109
+ # @param organization_slug [String] the slug of the organization the client keys belong to.
110
+ # @return [SentryApi::ObjectifiedHash]
111
+ def create_client_key(project_slug, options={}, organization_slug="")
112
+ organization_slug = @default_org_slug if organization_slug == ""
113
+ post("/projects/#{organization_slug}/#{project_slug}/keys/", body: options)
114
+ end
115
+
116
+ # Delete a Client Key.
117
+ #
118
+ # @example
119
+ # SentryApi.delete_client_key('project-slug','87c990582e07446b9907b357fc27730e')
120
+ #
121
+ # @param project_slug [String] the slug of the project the client keys belong to.
122
+ # @param key_id [String] the ID of the key to delete.
123
+ # @param organization_slug [String] the slug of the organization the client keys belong to.
124
+ def delete_client_key(project_slug, key_id, organization_slug="")
125
+ organization_slug = @default_org_slug if organization_slug == ""
126
+ delete("/projects/#{organization_slug}/#{project_slug}/keys/#{key_id}/")
127
+ end
128
+
129
+ # Update a Client Key
130
+ #
131
+ # @example
132
+ # SentryApi.update_client_key('project-slug','87c990582e07446b9907b357fc27730e',{name:'new-name'})
133
+ #
134
+ # @param project_slug [String] the slug of the project the client keys belong to.
135
+ # @param key_id [String] the ID of the key to update.
136
+ # @param [Hash] options A customizable set of options.
137
+ # @option options [String] :name the new name for the client key.
138
+ # @param organization_slug [String] the slug of the organization the client keys belong to.
139
+ # @return [Array<SentryApi::ObjectifiedHash>]
140
+ def update_client_key(project_slug, key_id, options={}, organization_slug="")
141
+ organization_slug = @default_org_slug if organization_slug == ""
142
+ put("/projects/#{organization_slug}/#{project_slug}/keys/#{key_id}/", body: options)
143
+ end
144
+
145
+ # Return a list of sampled events bound to a project.
146
+ #
147
+ # @example
148
+ # SentryApi.project_events('project-slug')
149
+ #
150
+ # @param project_slug [String] the slug of the project the client keys belong to.
151
+ # @param organization_slug [String] the slug of the organization the client keys belong to.
152
+ # @return [Array<SentryApi::ObjectifiedHash>]
153
+ def project_events(project_slug, organization_slug="")
154
+ organization_slug = @default_org_slug if organization_slug == ""
155
+ get("/projects/#{organization_slug}/#{project_slug}/events/")
156
+ end
157
+
158
+ # Retrieve an Event for a Project
159
+ #
160
+ # @example
161
+ # SentryApi.project_event('project-slug', 'event-id')
162
+ #
163
+ # @param project_slug [String] the slug of the project the client keys belong to.
164
+ # @param event_id [String] the slug of the project the event belongs to.
165
+ # @param organization_slug [String] the slug of the organization the client keys belong to.
166
+ # @return [SentryApi::ObjectifiedHash]
167
+ def project_event(project_slug, event_id, organization_slug="")
168
+ organization_slug = @default_org_slug if organization_slug == ""
169
+ get("/projects/#{organization_slug}/#{project_slug}/events/#{event_id}/")
170
+ end
171
+
172
+ # Return a list of aggregates bound to a project
173
+ #
174
+ # @example
175
+ # SentryApi.project_issues('project-slug')
176
+ #
177
+ # @return [Array<SentryApi::ObjectifiedHash>]
178
+ def project_issues(project_slug, organization_slug="")
179
+ organization_slug = @default_org_slug if organization_slug == ""
180
+ get("/projects/#{organization_slug}/#{project_slug}/issues/")
181
+ end
182
+
183
+ end
184
+
185
+ end
@@ -0,0 +1,24 @@
1
+ class SentryApi::Client
2
+
3
+ module Teams
4
+
5
+ # Create a new project bound to a team.
6
+ #
7
+ # @example
8
+ # SentryApi.create_project('team-slug',{name:'new-name'})
9
+ #
10
+ # @param team_slug [String] the slug of the team to create a new project for.
11
+ # @param organization_slug [String] the slug of the organization the team belongs to.
12
+ # @param [Hash] options A customizable set of options.
13
+ # @option options [String] :name the name for the new project.
14
+ # @option options [String] :slug optionally a slug for the new project. If it’s not provided a slug is generated from the name.
15
+ # @return [SentryApi::ObjectifiedHash]
16
+ def create_project(team_slug, options={}, organization_slug="")
17
+ organization_slug = @default_org_slug if organization_slug == ""
18
+ post("/teams/#{organization_slug}/#{team_slug}/projects/", body: options)
19
+ end
20
+
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,39 @@
1
+ module SentryApi
2
+ # Defines constants and methods related to configuration.
3
+ module Configuration
4
+ # An array of valid keys in the options hash when configuring a Sentry::API.
5
+ VALID_OPTIONS_KEYS = [:endpoint, :auth_token, :default_org_slug, :httparty].freeze
6
+
7
+ # The user agent that will be sent to the API endpoint if none is set.
8
+ DEFAULT_USER_AGENT = "Sentry Ruby Gem #{SentryApi::VERSION}".freeze
9
+
10
+ # @private
11
+ attr_accessor(*VALID_OPTIONS_KEYS)
12
+
13
+ # Sets all configuration options to their default values
14
+ # when this module is extended.
15
+ def self.extended(base)
16
+ base.reset
17
+ end
18
+
19
+ # Convenience method to allow configuration options to be set in a block.
20
+ def configure
21
+ yield self
22
+ end
23
+
24
+ # Creates a hash of options and their values.
25
+ def options
26
+ VALID_OPTIONS_KEYS.inject({}) do |option, key|
27
+ option.merge!(key => send(key))
28
+ end
29
+ end
30
+
31
+ # Resets all configuration options to the defaults.
32
+ def reset
33
+ self.endpoint = ENV['SENTRY_API_ENDPOINT']
34
+ self.auth_token = ENV['SENTRY_API_AUTH_TOKEN']
35
+ self.default_org_slug = ENV['SENTRY_API_DEFAULT_ORG_SLUG']
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,98 @@
1
+ module SentryApi
2
+ module Error
3
+ # Custom error class for rescuing from all Sentry errors.
4
+ class Error < StandardError;
5
+ end
6
+
7
+ # Raised when API endpoint credentials not configured.
8
+ class MissingCredentials < Error;
9
+ end
10
+
11
+ # Raised when impossible to parse response body.
12
+ class Parsing < Error;
13
+ end
14
+
15
+ # Custom error class for rescuing from HTTP response errors.
16
+ class ResponseError < Error
17
+ def initialize(response)
18
+ @response = response
19
+ super(build_error_message)
20
+ end
21
+
22
+ # Status code returned in the http response.
23
+ #
24
+ # @return [Integer]
25
+ def response_status
26
+ @response.code
27
+ end
28
+
29
+ private
30
+
31
+ # Human friendly message.
32
+ #
33
+ # @return [String]
34
+ def build_error_message
35
+ parsed_response = @response.parsed_response
36
+ message = parsed_response.message || parsed_response.error
37
+
38
+ "Server responded with code #{@response.code}, message: " \
39
+ "#{handle_message(message)}. " \
40
+ "Request URI: #{@response.request.base_uri}#{@response.request.path}"
41
+ end
42
+
43
+ # Handle error response message in case of nested hashes
44
+ def handle_message(message)
45
+ case message
46
+ when SentryApi::ObjectifiedHash
47
+ message.to_h.sort.map do |key, val|
48
+ "'#{key}' #{(val.is_a?(Hash) ? val.sort.map { |k, v| "(#{k}: #{v.join(' ')})" } : val).join(' ')}"
49
+ end.join(', ')
50
+ when Array
51
+ message.join(' ')
52
+ else
53
+ message
54
+ end
55
+ end
56
+ end
57
+
58
+ # Raised when API endpoint returns the HTTP status code 400.
59
+ class BadRequest < ResponseError;
60
+ end
61
+
62
+ # Raised when API endpoint returns the HTTP status code 401.
63
+ class Unauthorized < ResponseError;
64
+ end
65
+
66
+ # Raised when API endpoint returns the HTTP status code 403.
67
+ class Forbidden < ResponseError;
68
+ end
69
+
70
+ # Raised when API endpoint returns the HTTP status code 404.
71
+ class NotFound < ResponseError;
72
+ end
73
+
74
+ # Raised when API endpoint returns the HTTP status code 405.
75
+ class MethodNotAllowed < ResponseError;
76
+ end
77
+
78
+ # Raised when API endpoint returns the HTTP status code 409.
79
+ class Conflict < ResponseError;
80
+ end
81
+
82
+ # Raised when API endpoint returns the HTTP status code 422.
83
+ class Unprocessable < ResponseError;
84
+ end
85
+
86
+ # Raised when API endpoint returns the HTTP status code 500.
87
+ class InternalServerError < ResponseError;
88
+ end
89
+
90
+ # Raised when API endpoint returns the HTTP status code 502.
91
+ class BadGateway < ResponseError;
92
+ end
93
+
94
+ # Raised when API endpoint returns the HTTP status code 503.
95
+ class ServiceUnavailable < ResponseError;
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,35 @@
1
+ module SentryApi
2
+ # Converts hashes to the objects.
3
+ class ObjectifiedHash
4
+ # Creates a new ObjectifiedHash object.
5
+ def initialize(hash)
6
+ @hash = hash
7
+ @data = hash.inject({}) do |data, (key, value)|
8
+ value = ObjectifiedHash.new(value) if value.is_a? Hash
9
+ data[key.to_s] = value
10
+ data
11
+ end
12
+ end
13
+
14
+ # @return [Hash] The original hash.
15
+ def to_hash
16
+ @hash
17
+ end
18
+
19
+ alias_method :to_h, :to_hash
20
+
21
+ # @return [String] Formatted string with the class name, object id and original hash.
22
+ def inspect
23
+ "#<#{self.class}:#{object_id} {hash: #{@hash.inspect}}"
24
+ end
25
+
26
+ # Delegate to ObjectifiedHash.
27
+ def method_missing(key)
28
+ @data.key?(key.to_s) ? @data[key.to_s] : nil
29
+ end
30
+
31
+ def respond_to_missing?(method_name, include_private = false)
32
+ @hash.keys.map(&:to_sym).include?(method_name.to_sym) || super
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,33 @@
1
+ module SentryApi
2
+ # Parses link header.
3
+ #
4
+ # @private
5
+ class PageLinks
6
+ HEADER_LINK = 'Link'.freeze
7
+ DELIM_LINKS = ','.freeze
8
+ LINK_REGEX = /<([^>]+)>; rel=\"([^\"]+)\"; results=\"([^\"]+)\"/
9
+ METAS = %w(previous next)
10
+
11
+ attr_accessor(*METAS)
12
+
13
+ def initialize(headers)
14
+ link_header = headers[HEADER_LINK]
15
+
16
+ if link_header && link_header =~ /(previous|next)/
17
+ extract_links(link_header)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def extract_links(header)
24
+ header.split(DELIM_LINKS).each do |link|
25
+ LINK_REGEX.match(link.strip) do |match|
26
+ url, meta, results = match[1], match[2], match[3]
27
+ next if !url or !meta or !(results == "true") or METAS.index(meta).nil?
28
+ self.send("#{meta}=", url)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,75 @@
1
+ module SentryApi
2
+ # Wrapper class of paginated response.
3
+ class PaginatedResponse
4
+ attr_accessor :client
5
+
6
+ def initialize(array)
7
+ @array = array
8
+ end
9
+
10
+ def ==(other)
11
+ @array == other
12
+ end
13
+
14
+ def inspect
15
+ @array.inspect
16
+ end
17
+
18
+ def method_missing(name, *args, &block)
19
+ if @array.respond_to?(name)
20
+ @array.send(name, *args, &block)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def respond_to_missing?(method_name, include_private = false)
27
+ super || @array.respond_to?(method_name, include_private)
28
+ end
29
+
30
+ def parse_headers!(headers)
31
+ @links = PageLinks.new headers
32
+ end
33
+
34
+ def each_page
35
+ current = self
36
+ yield current
37
+ while current.has_next_page?
38
+ current = current.next_page
39
+ yield current
40
+ end
41
+ end
42
+
43
+ def auto_paginate
44
+ response = block_given? ? nil : []
45
+ each_page do |page|
46
+ if block_given?
47
+ page.each do |item|
48
+ yield item
49
+ end
50
+ else
51
+ response += page
52
+ end
53
+ end
54
+ response
55
+ end
56
+
57
+ def has_next_page?
58
+ !(@links.nil? || @links.next.nil?)
59
+ end
60
+
61
+ def next_page
62
+ return nil if @client.nil? || !has_next_page?
63
+ @links.next
64
+ end
65
+
66
+ def has_prev_page?
67
+ !(@links.nil? || @links.previous.nil?)
68
+ end
69
+
70
+ def prev_page
71
+ return nil if @client.nil? || !has_prev_page?
72
+ @links.previous
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,133 @@
1
+ require 'httparty'
2
+ require 'json'
3
+
4
+ module SentryApi
5
+ # @private
6
+ class Request
7
+ include HTTParty
8
+ format :json
9
+ headers 'Content-Type' => 'application/json'
10
+ parser proc { |body, _| parse(body) }
11
+
12
+ attr_accessor :auth_token, :endpoint, :default_org_slug
13
+
14
+ # Converts the response body to an ObjectifiedHash.
15
+ def self.parse(body)
16
+ body = decode(body)
17
+
18
+ if body.is_a? Hash
19
+ ObjectifiedHash.new body
20
+ elsif body.is_a? Array
21
+ if body[0].is_a? Array
22
+ body
23
+ else
24
+ PaginatedResponse.new(body.collect! { |e| ObjectifiedHash.new(e) })
25
+ end
26
+ elsif body
27
+ true
28
+ elsif !body
29
+ false
30
+ elsif body.nil?
31
+ false
32
+ else
33
+ raise Error::Parsing.new "Couldn't parse a response body"
34
+ end
35
+ end
36
+
37
+ # Decodes a JSON response into Ruby object.
38
+ def self.decode(response)
39
+ JSON.load response
40
+ rescue JSON::ParserError
41
+ raise Error::Parsing.new "The response is not a valid JSON"
42
+ end
43
+
44
+ def get(path, options={})
45
+ set_httparty_config(options)
46
+ set_authorization_header(options)
47
+ validate self.class.get(@endpoint + path, options)
48
+ end
49
+
50
+ def post(path, options={})
51
+ set_httparty_config(options)
52
+ set_json_body(options)
53
+ set_authorization_header(options, path)
54
+ validate self.class.post(@endpoint + path, options)
55
+ end
56
+
57
+ def put(path, options={})
58
+ set_httparty_config(options)
59
+ set_json_body(options)
60
+ set_authorization_header(options)
61
+ validate self.class.put(@endpoint + path, options)
62
+ end
63
+
64
+ def delete(path, options={})
65
+ set_httparty_config(options)
66
+ set_authorization_header(options)
67
+ validate self.class.delete(@endpoint + path, options)
68
+ end
69
+
70
+ # Checks the response code for common errors.
71
+ # Returns parsed response for successful requests.
72
+ def validate(response)
73
+ error_klass = case response.code
74
+ when 400 then
75
+ Error::BadRequest
76
+ when 401 then
77
+ Error::Unauthorized
78
+ when 403 then
79
+ Error::Forbidden
80
+ when 404 then
81
+ Error::NotFound
82
+ when 405 then
83
+ Error::MethodNotAllowed
84
+ when 409 then
85
+ Error::Conflict
86
+ when 422 then
87
+ Error::Unprocessable
88
+ when 500 then
89
+ Error::InternalServerError
90
+ when 502 then
91
+ Error::BadGateway
92
+ when 503 then
93
+ Error::ServiceUnavailable
94
+ end
95
+
96
+ fail error_klass.new(response) if error_klass
97
+
98
+ parsed = response.parsed_response
99
+ parsed.client = self if parsed.respond_to?(:client=)
100
+ parsed.parse_headers!(response.headers) if parsed.respond_to?(:parse_headers!)
101
+ parsed
102
+ end
103
+
104
+ # Sets a base_uri and default_params for requests.
105
+ # @raise [Error::MissingCredentials] if endpoint not set.
106
+ def set_request_defaults
107
+ self.class.default_params
108
+ raise Error::MissingCredentials.new("Please set an endpoint to API") unless @endpoint
109
+ end
110
+
111
+ private
112
+
113
+ # Sets a Authorization header for requests.
114
+ # @raise [Error::MissingCredentials] if auth_token and auth_token are not set.
115
+ def set_authorization_header(options, path=nil)
116
+ unless path == '/session'
117
+ raise Error::MissingCredentials.new("Please provide a auth_token for user") unless @auth_token
118
+ options[:headers] = {'Authorization' => "Bearer #{@auth_token}"}
119
+ end
120
+ end
121
+
122
+ # Set http post or put body as json string
123
+ def set_json_body(options)
124
+ options[:body] = options[:body].to_json
125
+ end
126
+
127
+ # Set HTTParty configuration
128
+ # @see https://github.com/jnunemaker/httparty
129
+ def set_httparty_config(options)
130
+ options.merge!(httparty) if httparty
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,3 @@
1
+ module SentryApi
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sentry-api/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sentry-api"
8
+ spec.version = SentryApi::VERSION
9
+ spec.authors = ["Thierry Xing"]
10
+ spec.email = ["thierry.xing@gmail.com"]
11
+ spec.licenses = ['MIT']
12
+ spec.summary = %q{Ruby client for Sentry API}
13
+ spec.description = %q{A Ruby wrapper for the Sentry API}
14
+ spec.homepage = "https://github.com/thierryxing/sentry-ruby-api"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
20
+ else
21
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
22
+ end
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.add_runtime_dependency 'httparty', "~> 0.14.0"
30
+ spec.add_development_dependency "bundler", "~> 1.12"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency 'rspec', "~> 3.5.0", '>= 3.5.0'
33
+ spec.add_development_dependency 'webmock', "~> 2.1.0", '>= 2.1.0'
34
+ spec.add_development_dependency 'yard', "~> 0.9.5"
35
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sentry-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Thierry Xing
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-08-31 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.14.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.14.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.5.0
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 3.5.0
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: 3.5.0
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 3.5.0
75
+ - !ruby/object:Gem::Dependency
76
+ name: webmock
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 2.1.0
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 2.1.0
85
+ type: :development
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: 2.1.0
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 2.1.0
95
+ - !ruby/object:Gem::Dependency
96
+ name: yard
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: 0.9.5
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: 0.9.5
109
+ description: A Ruby wrapper for the Sentry API
110
+ email:
111
+ - thierry.xing@gmail.com
112
+ executables: []
113
+ extensions: []
114
+ extra_rdoc_files: []
115
+ files:
116
+ - ".gitignore"
117
+ - ".travis.yml"
118
+ - Gemfile
119
+ - LICENSE.txt
120
+ - README.md
121
+ - Rakefile
122
+ - bin/console
123
+ - bin/setup
124
+ - lib/sentry-api.rb
125
+ - lib/sentry-api/api.rb
126
+ - lib/sentry-api/client.rb
127
+ - lib/sentry-api/client/events.rb
128
+ - lib/sentry-api/client/organizations.rb
129
+ - lib/sentry-api/client/projects.rb
130
+ - lib/sentry-api/client/teams.rb
131
+ - lib/sentry-api/configuration.rb
132
+ - lib/sentry-api/error.rb
133
+ - lib/sentry-api/objectified_hash.rb
134
+ - lib/sentry-api/page_links.rb
135
+ - lib/sentry-api/paginated_response.rb
136
+ - lib/sentry-api/request.rb
137
+ - lib/sentry-api/version.rb
138
+ - sentry-api.gemspec
139
+ homepage: https://github.com/thierryxing/sentry-ruby-api
140
+ licenses:
141
+ - MIT
142
+ metadata:
143
+ allowed_push_host: https://rubygems.org
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 2.5.1
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: Ruby client for Sentry API
164
+ test_files: []