├── .gitignore ├── .rakeTasks ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib ├── sentry-api.rb └── sentry-api │ ├── api.rb │ ├── client.rb │ ├── client │ ├── events.rb │ ├── issues.rb │ ├── organizations.rb │ ├── projects.rb │ ├── releases.rb │ └── teams.rb │ ├── configuration.rb │ ├── error.rb │ ├── objectified_hash.rb │ ├── page_links.rb │ ├── paginated_response.rb │ ├── request.rb │ └── version.rb ├── sentry-api.gemspec └── spec ├── fixtures ├── client_keys.json ├── create_client_key.json ├── create_team.json ├── delete_client_key.json ├── delete_project.json ├── issue.json ├── issue_events.json ├── issue_hashes.json ├── latest_event.json ├── oldest_event.json ├── organization.json ├── organization_projects.json ├── organization_stats.json ├── organizations.json ├── project.json ├── project_dsym_files.json ├── project_event.json ├── project_events.json ├── project_issues.json ├── project_stats.json ├── projects.json ├── release.json ├── remove_issue.json ├── team.json ├── update_client_key.json ├── update_issue.json ├── update_organization.json └── update_project.json ├── sentry-api ├── client │ ├── events_spec.rb │ ├── issues_spec.rb │ ├── organizations_spec.rb │ ├── projects_spec.rb │ ├── releases_spec.rb │ └── teams_spec.rb ├── error_spec.rb ├── objectified_hash_spec.rb ├── page_links_spec.rb ├── paginated_response_spec.rb └── request_spec.rb ├── sentry_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /.rakeTasks: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.0 4 | - 2.1 5 | - 2.2 6 | - 2.3 7 | - 2.4 8 | - 2.5 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in sentry.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2016 Thierry Xing 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sentry Ruby API 2 | [](https://travis-ci.org/thierryxing/sentry-ruby-api) 3 | [](https://badge.fury.io/rb/sentry-api) 4 | [](https://github.com/thierryxing/sentry-ruby-api/blob/master/LICENSE.txt) 5 | 6 | Sentry Ruby API is a Ruby wrapper for the [getsentry/sentry API](https://docs.sentry.io/hosted/api/). 7 | 8 | 9 | ## Installation 10 | Install it from rubygems: 11 | 12 | ```sh 13 | gem install sentry-api 14 | ``` 15 | 16 | Or add to a Gemfile: 17 | 18 | ```ruby 19 | gem 'sentry-api' 20 | ``` 21 | 22 | ## Usage 23 | 24 | Configuration example: 25 | 26 | ```ruby 27 | SentryApi.configure do |config| 28 | config.endpoint = 'http://example.com/api/0' 29 | config.auth_token = 'your_auth_token' 30 | config.default_org_slug = 'sentry-sc' 31 | end 32 | ``` 33 | 34 | (Note: If you are using getsentry.com's hosted service, your endpoint will be `https://sentry.io/api/0/`) 35 | 36 | Usage examples: 37 | 38 | ```ruby 39 | # set an API endpoint 40 | SentryApi.endpoint = 'http://example.com/api/0' 41 | # => "http://example.com/api/0" 42 | 43 | # set a user private token 44 | SentryApi.auth_token = 'your_auth_token' 45 | # => "your_auth_token" 46 | 47 | # configure a proxy server 48 | SentryApi.http_proxy('proxyhost', 8888) 49 | # proxy server w/ basic auth 50 | SentryApi.http_proxy('proxyhost', 8888, 'user', 'pass') 51 | 52 | # list projects 53 | SentryApi.projects 54 | 55 | # initialize a new client 56 | s = SentryApi.client(endpoint: 'https://api.example.com', auth_token: 'your_auth_token', default_org_slug: 'sentry-sc') 57 | 58 | # a paginated response 59 | projects = SentryApi.projects 60 | 61 | # check existence of the next page 62 | projects.has_next_page? 63 | 64 | # retrieve the next page 65 | projects.next_page 66 | 67 | # iterate all projects 68 | projects.auto_paginate do |project| 69 | # do something 70 | end 71 | 72 | # retrieve all projects as an array 73 | projects.auto_paginate 74 | ``` 75 | 76 | ## Development 77 | After checking out the repo, run `bin/setup` to install dependencies. Then, run 78 | `rake spec` to run the tests. You can also run `bin/console` for an interactive 79 | prompt that will allow you to experiment. 80 | 81 | ## License 82 | 83 | Released under the BSD 2-clause license. See LICENSE.txt for details. 84 | 85 | ## Special Thank 86 | Thanks to NARKOZ's [gitlab](https://github.com/NARKOZ/gitlab) ruby wrapper which really gives me a lot of inspiration. 87 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | task :default => :spec 3 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 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 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 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 9 | -------------------------------------------------------------------------------- /lib/sentry-api.rb: -------------------------------------------------------------------------------- 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 | extend self 14 | 15 | # Alias for Sentry::Client.new 16 | # 17 | # @return [Sentry::Client] 18 | def self.client(options={}) 19 | options.empty? ? empty_options_client : SentryApi::Client.new(options) 20 | end 21 | 22 | # Delegate to Sentry::Client 23 | def self.method_missing(method, *args, &block) 24 | return super unless client.respond_to?(method) 25 | client.send(method, *args, &block) 26 | end 27 | 28 | # Delegate to Sentry::Client 29 | def respond_to_missing?(method_name, include_private = false) 30 | client.respond_to?(method_name) || super 31 | end 32 | 33 | # Delegate to HTTParty.http_proxy 34 | def self.http_proxy(address=nil, port=nil, username=nil, password=nil) 35 | SentryApi::Request.http_proxy(address, port, username, password) 36 | end 37 | 38 | # Returns an unsorted array of available client methods. 39 | # 40 | # @return [Array] 41 | def self.actions 42 | hidden = /endpoint|auth_token|default_org_slug|get|post|put|delete|validate|set_request_defaults|httparty/ 43 | (SentryApi::Client.instance_methods - Object.methods).reject { |e| e[hidden] } 44 | end 45 | 46 | private 47 | 48 | def empty_options_client 49 | @client ||= SentryApi::Client.new({}) 50 | end 51 | end -------------------------------------------------------------------------------- /lib/sentry-api/api.rb: -------------------------------------------------------------------------------- 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 18 | -------------------------------------------------------------------------------- /lib/sentry-api/client.rb: -------------------------------------------------------------------------------- 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 Issues 9 | include Events 10 | include Teams 11 | include Releases 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/sentry-api/client/events.rb: -------------------------------------------------------------------------------- 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] 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] 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 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 92 | -------------------------------------------------------------------------------- /lib/sentry-api/client/issues.rb: -------------------------------------------------------------------------------- 1 | class SentryApi::Client 2 | module Issues 3 | # List Issues 4 | # 5 | # @example 6 | # SentryApi.project_issues('project-slug', {'query': 'is:unresolved Build-version:6.5.0'}) 7 | # 8 | # @param project_slug [String] the slug of the project the client keys belong to. 9 | # @param [Hash] options A customizable set of options. @option options [String] :statsPeriod an optional stat period (can be one of "24h", "14d", and ""). 10 | # @option options [String] :query an optional Sentry structured search query. If not provided an implied "is:resolved" is assumed.) 11 | # @return [Array] 12 | def issues(project_slug, options={}) 13 | get("/projects/#{@default_org_slug}/#{project_slug}/issues/", query: options) 14 | end 15 | 16 | # Batch update issues 17 | # 18 | # @example 19 | # SentryApi.update_client_key('project-slug', ['123', '456'], status:'ignored') 20 | # 21 | # @param project_slug [String] the slug of the project the client keys belong to. 22 | # @param issue_ids [Array] An array of issue ids which are to be updated. 23 | # @option options [Object] An object containing the issue fields which are to be updated. 24 | # @return [Array] 25 | def batch_update_issues(project_slug, issue_ids=[], options={}) 26 | put("/projects/#{@default_org_slug}/#{project_slug}/issues/?id=#{issue_ids.join('&id=')}", body: options) 27 | end 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /lib/sentry-api/client/organizations.rb: -------------------------------------------------------------------------------- 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] 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 | # @return [Array] 22 | def organization_projects 23 | get("/organizations/#{@default_org_slug}/projects/") 24 | end 25 | 26 | # Retrieve an Organization 27 | # 28 | # @example 29 | # SentryApi.organization 30 | # SentryApi.organization('slug') 31 | # 32 | # @return [SentryApi::ObjectifiedHash] 33 | def organization 34 | get("/organizations/#{@default_org_slug}/") 35 | end 36 | 37 | # Update an Organization 38 | # 39 | # @example 40 | # SentryApi.update_organization('slug') 41 | # SentryApi.update_organization('slug',{name:'new-name'}) 42 | # SentryApi.update_organization('slug',{name:'new-name', slug:'new-slug'}) 43 | # 44 | # @param [Hash] options A customizable set of options. 45 | # @option options [String] :name an optional new name for the organization. 46 | # @option options [String] :slug an optional new slug for the organization. Needs to be available and unique. 47 | # @return [SentryApi::ObjectifiedHash] 48 | def update_organization(options={}) 49 | put("/organizations/#{@default_org_slug}/", body: options) 50 | end 51 | 52 | # Retrieve Event Counts for an Organization 53 | # 54 | # @example 55 | # SentryApi.organization_stats('slug') 56 | # SentryApi.organization_stats('slug', {stat:'received', since:'1472158800'}) 57 | # 58 | # @param [Hash] options A customizable set of options. 59 | # @option options [String] :stat the name of the stat to query ("received", "rejected", "blacklisted") 60 | # @option options [Timestamp] :since a timestamp to set the start of the query in seconds since UNIX epoch. 61 | # @option options [Timestamp] :until a timestamp to set the end of the query in seconds since UNIX epoch. 62 | # @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. 63 | # @return [Array] 64 | def organization_stats(options={}) 65 | get("/organizations/#{@default_org_slug}/stats/", query: options) 66 | end 67 | 68 | # Create a new team bound to an organization 69 | # 70 | # @example 71 | # SentryApi.create_project('team-slug', {name:'team-name', slug:'team-slug'}) 72 | # 73 | # @param [Hash] options A customizable set of options. 74 | # @option options [String] :name the name for the new team. 75 | # @option options [String] :slug optionally a slug for the new team. If it’s not provided a slug is generated from the name. 76 | # @return [SentryApi::ObjectifiedHash] 77 | def create_team(options={}) 78 | post("/organizations/#{@default_org_slug}/teams/", body: options) 79 | end 80 | 81 | # Return a list of teams bound to a organization. 82 | # 83 | # @example 84 | # SentryApi.organization_teams('team-slug') 85 | # 86 | # @return [Array] 87 | def organization_teams 88 | get("/organizations/#{@default_org_slug}/teams/") 89 | end 90 | 91 | end 92 | 93 | end -------------------------------------------------------------------------------- /lib/sentry-api/client/projects.rb: -------------------------------------------------------------------------------- 1 | class SentryApi::Client 2 | 3 | module Projects 4 | # List your Projects 5 | # 6 | # @example 7 | # SentryApi.projects 8 | # 9 | # @return [Array] 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 | # @return [SentryApi::ObjectifiedHash] 21 | def project(project_slug) 22 | get("/projects/#{@default_org_slug}/#{project_slug}/") 23 | end 24 | 25 | # Update a Project 26 | # 27 | # @example 28 | # SentryApi.update_project('project-slug', {name:'new-name', slug:'new-slug', is_bookmarked:false}) 29 | # 30 | # @param project_slug [String] the slug of the project to retrieve. 31 | # @param [Hash] options A customizable set of options. 32 | # @option options [String] :name the new name for the project. 33 | # @option options [String] :slug the new slug for the project. 34 | # @option options [String] :isBookmarked in case this API call is invoked with a user context this allows changing of the bookmark flag. 35 | # @option options [Hash] optional options to override in the project settings. 36 | # @return [SentryApi::ObjectifiedHash] 37 | def update_project(project_slug, options={}) 38 | put("/projects/#{@default_org_slug}/#{project_slug}/", body: options) 39 | end 40 | 41 | # Delete a Project. 42 | # 43 | # @example 44 | # SentryApi.delete_project('project-slug') 45 | # 46 | # @param project_slug [String] the slug of the project to delete. 47 | def delete_project(project_slug) 48 | delete("/projects/#{@default_org_slug}/#{project_slug}/") 49 | end 50 | 51 | # Retrieve Event Counts for an Project 52 | # 53 | # @example 54 | # SentryApi.project_stats('slug') 55 | # SentryApi.project_stats('slug', {stat:'received', since:'1472158800'}) 56 | # 57 | # @param project_slug [String] the slug of the project. 58 | # @param [Hash] options A customizable set of options. 59 | # @option options [String] :stat the name of the stat to query ("received", "rejected", "blacklisted") 60 | # @option options [Timestamp] :since a timestamp to set the start of the query in seconds since UNIX epoch. 61 | # @option options [Timestamp] :until a timestamp to set the end of the query in seconds since UNIX epoch. 62 | # @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. 63 | # @return [Array] 64 | def project_stats(project_slug, options={}) 65 | get("/projects/#{@default_org_slug}/#{project_slug}/stats/", query: options) 66 | end 67 | 68 | # Upload a new dsym file for the given release 69 | # 70 | # @example 71 | # SentryApi.upload_dsym_files('project-slug','/path/to/file') 72 | # 73 | # @param project_slug [String] the slug of the project to list the dsym files of. 74 | # @param file_path [String] the absolute file path of the dsym file. 75 | # @param organization_slug [String] the slug of the organization. 76 | # @return [Array] 77 | def upload_dsym_files(project_slug, file_path) 78 | upload("/projects/#{@default_org_slug}/#{project_slug}/files/dsyms/", body: {file: File.new(file_path)}) 79 | end 80 | 81 | # List a Project’s DSym Files. 82 | # 83 | # @example 84 | # SentryApi.project_dsym_files('project-slug') 85 | # 86 | # @param project_slug [String] the slug of the project to list the dsym files of. 87 | # @return [Array] 88 | def project_dsym_files(project_slug) 89 | get("/projects/#{@default_org_slug}/#{project_slug}/files/dsyms/") 90 | end 91 | 92 | # List a Project’s Client Keys. 93 | # 94 | # @example 95 | # SentryApi.client_keys('project-slug') 96 | # 97 | # @param project_slug [String] the slug of the project the client keys belong to. 98 | # @return [Array] 99 | def client_keys(project_slug) 100 | get("/projects/#{@default_org_slug}/#{project_slug}/keys/") 101 | end 102 | 103 | # Create a new Client Key. 104 | # 105 | # @example 106 | # SentryApi.create_client_key('project-slug','new-name') 107 | # 108 | # @param project_slug [String] the slug of the project the client keys belong to. 109 | # @param [Hash] options A customizable set of options. 110 | # @option options [String] :name the name for the new key. 111 | # @return [SentryApi::ObjectifiedHash] 112 | def create_client_key(project_slug, options={}) 113 | post("/projects/#{@default_org_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 | def delete_client_key(project_slug, key_id) 124 | delete("/projects/#{@default_org_slug}/#{project_slug}/keys/#{key_id}/") 125 | end 126 | 127 | # Update a Client Key 128 | # 129 | # @example 130 | # SentryApi.update_client_key('project-slug','87c990582e07446b9907b357fc27730e',{name:'new-name'}) 131 | # 132 | # @param project_slug [String] the slug of the project the client keys belong to. 133 | # @param key_id [String] the ID of the key to update. 134 | # @param [Hash] options A customizable set of options. 135 | # @option options [String] :name the new name for the client key. 136 | # @return [Array] 137 | def update_client_key(project_slug, key_id, options={}) 138 | put("/projects/#{@default_org_slug}/#{project_slug}/keys/#{key_id}/", body: options) 139 | end 140 | 141 | # Return a list of sampled events bound to a project. 142 | # 143 | # @example 144 | # SentryApi.project_events('project-slug') 145 | # 146 | # @param project_slug [String] the slug of the project the client keys belong to. 147 | # @return [Array] 148 | def project_events(project_slug) 149 | get("/projects/#{@default_org_slug}/#{project_slug}/events/") 150 | end 151 | 152 | # Return a list of issues (groups) bound to a project. All parameters are supplied as query string parameters. 153 | # 154 | # @example 155 | # SentryApi.project_event('project-slug', 'event-id') 156 | # 157 | # @param project_slug [String] the slug of the project the client keys belong to. 158 | # @param event_id [String] the hexadecimal ID of the event to retrieve (as reported by the raven client) 159 | # @return [SentryApi::ObjectifiedHash] 160 | def project_event(project_slug, event_id) 161 | get("/projects/#{@default_org_slug}/#{project_slug}/events/#{event_id}/") 162 | end 163 | 164 | # List a Project’s Issues 165 | # 166 | # @example 167 | # SentryApi.project_issues('project-slug', {'query': 'is:unresolved Build-version:6.5.0'}) 168 | # 169 | # @param project_slug [String] the slug of the project the client keys belong to. 170 | # @param [Hash] options A customizable set of options. 171 | # @option options [String] :statsPeriod an optional stat period (can be one of "24h", "14d", and ""). 172 | # @option options [String] :query an optional Sentry structured search query. If not provided an implied "is:resolved" is assumed.) 173 | # @return [Array] 174 | def project_issues(project_slug, options={}) 175 | get("/projects/#{@default_org_slug}/#{project_slug}/issues/", query: options) 176 | end 177 | 178 | end 179 | 180 | end 181 | -------------------------------------------------------------------------------- /lib/sentry-api/client/releases.rb: -------------------------------------------------------------------------------- 1 | class SentryApi::Client 2 | 3 | module Releases 4 | 5 | # Create a new release for the given project. 6 | # Releases are used by Sentry to improve it’s error reporting abilities by correlating first seen events with the release that might have introduced the problem. 7 | # 8 | # @example 9 | # SentryApi.create_release('project-slug',{version:'1.0', ref:'6ba09a7c53235ee8a8fa5ee4c1ca8ca886e7fdbb'}) 10 | # 11 | # @param project_slug [String] the slug of the project the client keys belong to. 12 | # @param [Hash] options A customizable set of options. 13 | # @option options [String] :version a version identifier for this release. Can be a version number, a commit hash etc. 14 | # @option options [String] :ref an optional commit reference. This is useful if a tagged version has been provided. 15 | # @option options [String] :url a URL that points to the release. This can be the path to an online interface to the sourcecode for instance. 16 | # @option options [Timestamp] :dateStarted an optional date that indicates when the release process started. 17 | # @option options [Timestamp] :dateReleased an optional date that indicates when the release went live. If not provided the current time is assumed. 18 | # @return 19 | def create_release(project_slug, options={}) 20 | post("/projects/#{@default_org_slug}/#{project_slug}/releases/", body: options) 21 | end 22 | 23 | # Permanently remove a release and all of its files. 24 | # 25 | # @example 26 | # SentryApi.delete_release('project-slug','1.0') 27 | # 28 | # @param project_slug [String] the slug of the project to delete the release of. 29 | # @param version [String] the version identifier of the release. 30 | def delete_release(project_slug, version) 31 | delete("/projects/#{@default_org_slug}/#{project_slug}/releases/#{version}/") 32 | end 33 | 34 | # List a Project’s Releases 35 | # 36 | # @example 37 | # SentryApi.releases('project-slug') 38 | # 39 | # @param project_slug [String] the slug of the project to list the releases of. 40 | # @return [Array] 41 | def releases(project_slug) 42 | get("/projects/#{@default_org_slug}/#{project_slug}/releases/") 43 | end 44 | 45 | # Retrieve a Release 46 | # 47 | # @example 48 | # SentryApi.release('project-slug','1.0') 49 | # 50 | # @param project_slug [String] the slug of the project to retrieve the release of. 51 | # @param version [String] the version identifier of the release. 52 | # @return 53 | def release(project_slug, version) 54 | get("/projects/#{@default_org_slug}/#{project_slug}/releases/#{version}/") 55 | end 56 | 57 | # Update a Release 58 | # 59 | # @example 60 | # SentryApi.update('project-slug', {ref:'6ba09a7c53235ee8a8fa5ee4c1ca8ca886e7fdbb'}) 61 | # 62 | # @param project_slug [String] the slug of the project to retrieve the release of. 63 | # @param version [String] the version identifier of the release. 64 | # @option options [String] :ref an optional commit reference. This is useful if a tagged version has been provided. 65 | # @option options [String] :url a URL that points to the release. This can be the path to an online interface to the sourcecode for instance. 66 | # @option options [Timestamp] :dateStarted an optional date that indicates when the release process started. 67 | # @option options [Timestamp] :dateReleased an optional date that indicates when the release went live. If not provided the current time is assumed. 68 | # @return 69 | def update_release(project_slug, version, options={}) 70 | put("/projects/#{@default_org_slug}/#{project_slug}/releases/#{version}/", body: options) 71 | end 72 | 73 | end 74 | 75 | end -------------------------------------------------------------------------------- /lib/sentry-api/client/teams.rb: -------------------------------------------------------------------------------- 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:'team-name'}) 9 | # 10 | # @param team_slug [String] the slug of the team 11 | # @param [Hash] options A customizable set of options. 12 | # @option options [String] :name the name for the new project. 13 | # @option options [String] :slug optionally a slug for the new project. If it’s not provided a slug is generated from the name. 14 | # @return [SentryApi::ObjectifiedHash] 15 | def create_project(team_slug, options={}) 16 | post("/teams/#{@default_org_slug}/#{team_slug}/projects/", body: options) 17 | end 18 | 19 | # Schedules a team for deletion 20 | # 21 | # @example 22 | # SentryApi.delete_team('team-slug') 23 | # 24 | # @param team_slug [String] the slug of the team 25 | def delete_team(team_slug) 26 | delete("/teams/#{@default_org_slug}/#{team_slug}/") 27 | end 28 | 29 | # Return a list of projects bound to a team 30 | # 31 | # @example 32 | # SentryApi.delete_team('team-slug') 33 | # 34 | # @param team_slug [String] the slug of the team 35 | # @return [Array] 36 | def team_projects(team_slug) 37 | get("/teams/#{@default_org_slug}/#{team_slug}/projects/") 38 | end 39 | 40 | # Return details on an individual team. 41 | # 42 | # @example 43 | # SentryApi.team_projects('team-slug') 44 | # 45 | # @param team_slug [String] the slug of the team 46 | # @return [SentryApi::ObjectifiedHash] 47 | def team(team_slug) 48 | get("/teams/#{@default_org_slug}/#{team_slug}/") 49 | end 50 | 51 | # Update various attributes and configurable settings for the given team. 52 | # 53 | # @example 54 | # SentryApi.update_team('team-slug', {name:'team-name'}) 55 | # 56 | # @param team_slug [String] the slug of the team 57 | # @param [Hash] options A customizable set of options. 58 | # @option options [String] :name the name for the new project. 59 | # @option options [String] :slug optionally a slug for the new project. If it’s not provided a slug is generated from the name. 60 | # @return [SentryApi::ObjectifiedHash] 61 | def update_team(team_slug, options={}) 62 | get("/teams/#{@default_org_slug}/#{team_slug}/", body: options) 63 | end 64 | 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/sentry-api/configuration.rb: -------------------------------------------------------------------------------- 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 40 | -------------------------------------------------------------------------------- /lib/sentry-api/error.rb: -------------------------------------------------------------------------------- 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 99 | -------------------------------------------------------------------------------- /lib/sentry-api/objectified_hash.rb: -------------------------------------------------------------------------------- 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 36 | -------------------------------------------------------------------------------- /lib/sentry-api/page_links.rb: -------------------------------------------------------------------------------- 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 34 | -------------------------------------------------------------------------------- /lib/sentry-api/paginated_response.rb: -------------------------------------------------------------------------------- 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 | path = @links.next.sub(/#{@client.endpoint}/, '') 64 | @client.get(path) 65 | end 66 | 67 | def has_prev_page? 68 | !(@links.nil? || @links.previous.nil?) 69 | end 70 | 71 | def prev_page 72 | return nil if @client.nil? || !has_prev_page? 73 | path = @links.previous.sub(/#{@client.endpoint}/, '') 74 | @client.get(path) 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/sentry-api/request.rb: -------------------------------------------------------------------------------- 1 | require 'httmultiparty' 2 | require 'json' 3 | 4 | module SentryApi 5 | # @private 6 | class Request 7 | include HTTMultiParty 8 | 9 | format :json 10 | headers "Content-Type" => "application/json" 11 | parser proc { |body, _| parse(body) } 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 | def upload(path, options={}) 71 | set_httparty_config(options) 72 | set_authorization_header(options) 73 | validate self.class.post(@endpoint + path, options) 74 | end 75 | 76 | # Checks the response code for common errors. 77 | # Returns parsed response for successful requests. 78 | def validate(response) 79 | error_klass = case response.code 80 | when 400 then 81 | Error::BadRequest 82 | when 401 then 83 | Error::Unauthorized 84 | when 403 then 85 | Error::Forbidden 86 | when 404 then 87 | Error::NotFound 88 | when 405 then 89 | Error::MethodNotAllowed 90 | when 409 then 91 | Error::Conflict 92 | when 422 then 93 | Error::Unprocessable 94 | when 500 then 95 | Error::InternalServerError 96 | when 502 then 97 | Error::BadGateway 98 | when 503 then 99 | Error::ServiceUnavailable 100 | end 101 | 102 | fail error_klass.new(response) if error_klass 103 | 104 | parsed = response.parsed_response 105 | parsed.client = self if parsed.respond_to?(:client=) 106 | parsed.parse_headers!(response.headers) if parsed.respond_to?(:parse_headers!) 107 | parsed 108 | end 109 | 110 | # Sets a base_uri and default_params for requests. 111 | # @raise [Error::MissingCredentials] if endpoint not set. 112 | def set_request_defaults 113 | self.class.default_params 114 | raise Error::MissingCredentials.new("Please set an endpoint to API") unless @endpoint 115 | end 116 | 117 | private 118 | 119 | # Sets a Authorization header for requests. 120 | # @raise [Error::MissingCredentials] if auth_token and auth_token are not set. 121 | def set_authorization_header(options, path=nil) 122 | unless path == '/session' 123 | raise Error::MissingCredentials.new("Please provide a auth_token for user") unless @auth_token 124 | options[:headers] = {'Authorization' => "Bearer #{@auth_token}"} 125 | end 126 | end 127 | 128 | # Set http post or put body as json string if content type is application/json 129 | def set_json_body(options) 130 | headers = self.class.headers 131 | if headers and headers["Content-Type"] == "application/json" 132 | options[:body] = options[:body].to_json 133 | end 134 | end 135 | 136 | # Set HTTParty configuration 137 | # @see https://github.com/jnunemaker/httparty 138 | def set_httparty_config(options) 139 | options.merge!(httparty) if httparty 140 | end 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /lib/sentry-api/version.rb: -------------------------------------------------------------------------------- 1 | module SentryApi 2 | VERSION = "0.3.4" 3 | end 4 | -------------------------------------------------------------------------------- /sentry-api.gemspec: -------------------------------------------------------------------------------- 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 = ['BSD'] 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 'httmultiparty', "~> 0.3.16" 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 36 | -------------------------------------------------------------------------------- /spec/fixtures/client_keys.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "label": "Fabulous Key", 4 | "dsn": { 5 | "secret": "https://95729535ea81422dba6c951e714a0174:a6b380407fc5443fb30d239670f8dee8@app.getsentry.com/2", 6 | "csp": "https://app.getsentry.com/api/2/csp-report/?sentry_key=95729535ea81422dba6c951e714a0174", 7 | "public": "https://95729535ea81422dba6c951e714a0174@app.getsentry.com/2" 8 | }, 9 | "secret": "a6b380407fc5443fb30d239670f8dee8", 10 | "id": "95729535ea81422dba6c951e714a0174", 11 | "dateCreated": "2016-08-26T20:01:03.047Z", 12 | "public": "95729535ea81422dba6c951e714a0174" 13 | } 14 | ] -------------------------------------------------------------------------------- /spec/fixtures/create_client_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Fabulous Key", 3 | "dsn": { 4 | "secret": "https://95729535ea81422dba6c951e714a0174:a6b380407fc5443fb30d239670f8dee8@app.getsentry.com/2", 5 | "csp": "https://app.getsentry.com/api/2/csp-report/?sentry_key=95729535ea81422dba6c951e714a0174", 6 | "public": "https://95729535ea81422dba6c951e714a0174@app.getsentry.com/2" 7 | }, 8 | "secret": "a6b380407fc5443fb30d239670f8dee8", 9 | "id": "95729535ea81422dba6c951e714a0174", 10 | "dateCreated": "2016-08-26T20:01:03.047Z", 11 | "public": "95729535ea81422dba6c951e714a0174" 12 | } -------------------------------------------------------------------------------- /spec/fixtures/create_team.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "ancient-gabelers", 3 | "name": "Ancient Gabelers", 4 | "hasAccess": true, 5 | "isPending": false, 6 | "dateCreated": "2016-08-30T21:23:02.065Z", 7 | "isMember": false, 8 | "id": "3" 9 | } 10 | -------------------------------------------------------------------------------- /spec/fixtures/delete_client_key.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /spec/fixtures/delete_project.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /spec/fixtures/issue.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "unresolved", 3 | "lastSeen": "2016-08-30T01:11:03Z", 4 | "userReportCount": 0, 5 | "id": "1", 6 | "userCount": 0, 7 | "stats": { 8 | "30d": [ 9 | [ 10 | 1469923200, 11 | 12102 12 | ], 13 | [ 14 | 1470009600, 15 | 15787 16 | ], 17 | [ 18 | 1470096000, 19 | 13029 20 | ], 21 | [ 22 | 1470182400, 23 | 13118 24 | ], 25 | [ 26 | 1470268800, 27 | 12554 28 | ], 29 | [ 30 | 1470355200, 31 | 11602 32 | ], 33 | [ 34 | 1470441600, 35 | 14314 36 | ], 37 | [ 38 | 1470528000, 39 | 12500 40 | ], 41 | [ 42 | 1470614400, 43 | 12521 44 | ], 45 | [ 46 | 1470700800, 47 | 11587 48 | ], 49 | [ 50 | 1470787200, 51 | 12572 52 | ], 53 | [ 54 | 1470873600, 55 | 14235 56 | ], 57 | [ 58 | 1470960000, 59 | 14530 60 | ], 61 | [ 62 | 1471046400, 63 | 14487 64 | ], 65 | [ 66 | 1471132800, 67 | 12891 68 | ], 69 | [ 70 | 1471219200, 71 | 11672 72 | ], 73 | [ 74 | 1471305600, 75 | 14050 76 | ], 77 | [ 78 | 1471392000, 79 | 14193 80 | ], 81 | [ 82 | 1471478400, 83 | 13021 84 | ], 85 | [ 86 | 1471564800, 87 | 12347 88 | ], 89 | [ 90 | 1471651200, 91 | 13854 92 | ], 93 | [ 94 | 1471737600, 95 | 10842 96 | ], 97 | [ 98 | 1471824000, 99 | 11820 100 | ], 101 | [ 102 | 1471910400, 103 | 13747 104 | ], 105 | [ 106 | 1471996800, 107 | 11961 108 | ], 109 | [ 110 | 1472083200, 111 | 11927 112 | ], 113 | [ 114 | 1472169600, 115 | 15068 116 | ], 117 | [ 118 | 1472256000, 119 | 11397 120 | ], 121 | [ 122 | 1472342400, 123 | 13610 124 | ], 125 | [ 126 | 1472428800, 127 | 13459 128 | ], 129 | [ 130 | 1472515200, 131 | 1009 132 | ] 133 | ], 134 | "24h": [ 135 | [ 136 | 1472432400, 137 | 519 138 | ], 139 | [ 140 | 1472436000, 141 | 808 142 | ], 143 | [ 144 | 1472439600, 145 | 710 146 | ], 147 | [ 148 | 1472443200, 149 | 225 150 | ], 151 | [ 152 | 1472446800, 153 | 392 154 | ], 155 | [ 156 | 1472450400, 157 | 137 158 | ], 159 | [ 160 | 1472454000, 161 | 846 162 | ], 163 | [ 164 | 1472457600, 165 | 912 166 | ], 167 | [ 168 | 1472461200, 169 | 843 170 | ], 171 | [ 172 | 1472464800, 173 | 336 174 | ], 175 | [ 176 | 1472468400, 177 | 747 178 | ], 179 | [ 180 | 1472472000, 181 | 638 182 | ], 183 | [ 184 | 1472475600, 185 | 656 186 | ], 187 | [ 188 | 1472479200, 189 | 511 190 | ], 191 | [ 192 | 1472482800, 193 | 884 194 | ], 195 | [ 196 | 1472486400, 197 | 549 198 | ], 199 | [ 200 | 1472490000, 201 | 163 202 | ], 203 | [ 204 | 1472493600, 205 | 546 206 | ], 207 | [ 208 | 1472497200, 209 | 496 210 | ], 211 | [ 212 | 1472500800, 213 | 868 214 | ], 215 | [ 216 | 1472504400, 217 | 419 218 | ], 219 | [ 220 | 1472508000, 221 | 296 222 | ], 223 | [ 224 | 1472511600, 225 | 690 226 | ], 227 | [ 228 | 1472515200, 229 | 466 230 | ], 231 | [ 232 | 1472518800, 233 | 543 234 | ] 235 | ] 236 | }, 237 | "culprit": "raven.scripts.runner in main", 238 | "title": "This is an example python exception", 239 | "pluginActions": [], 240 | "assignedTo": null, 241 | "participants": [], 242 | "logger": null, 243 | "type": "default", 244 | "annotations": [], 245 | "metadata": { 246 | "title": "This is an example python exception" 247 | }, 248 | "seenBy": [], 249 | "tags": [], 250 | "numComments": 0, 251 | "isPublic": false, 252 | "permalink": "https://app.getsentry.com/the-interstellar-jurisdiction/pump-station/issues/1/", 253 | "firstRelease": { 254 | "dateReleased": null, 255 | "url": null, 256 | "ref": null, 257 | "owner": null, 258 | "dateCreated": "2016-08-30T01:11:03.621Z", 259 | "lastEvent": "2016-08-30T01:11:03.700Z", 260 | "version": "7a7b2005c80710928dde1104db37ca0cdf9538fa", 261 | "firstEvent": "2016-08-30T01:11:03.700Z", 262 | "shortVersion": "7a7b2005c807", 263 | "dateStarted": null, 264 | "newGroups": 0, 265 | "data": {} 266 | }, 267 | "shortId": "PUMP-STATION-1", 268 | "shareId": "322e31", 269 | "firstSeen": "2016-08-30T01:11:03Z", 270 | "count": "1", 271 | "hasSeen": false, 272 | "level": "error", 273 | "isSubscribed": false, 274 | "isBookmarked": false, 275 | "project": { 276 | "name": "Pump Station", 277 | "slug": "pump-station" 278 | }, 279 | "lastRelease": null, 280 | "activity": [ 281 | { 282 | "type": "first_seen", 283 | "user": null, 284 | "data": {}, 285 | "id": "None", 286 | "dateCreated": "2016-08-30T01:11:03Z" 287 | } 288 | ], 289 | "statusDetails": {} 290 | } -------------------------------------------------------------------------------- /spec/fixtures/issue_events.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "eventID": "03fc4d526a3d47c7b2219ca1f3c81cf7", 4 | "sdk": null, 5 | "errors": [], 6 | "platform": "python", 7 | "contexts": {}, 8 | "size": 7911, 9 | "dateCreated": "2016-08-30T01:11:03Z", 10 | "dateReceived": "2016-08-30T01:11:03Z", 11 | "user": { 12 | "username": "getsentry", 13 | "email": "foo@example.com", 14 | "id": "1671" 15 | }, 16 | "context": { 17 | "emptyList": [], 18 | "unauthorized": false, 19 | "emptyMap": {}, 20 | "url": "http://example.org/foo/bar/", 21 | "results": [ 22 | 1, 23 | 2, 24 | 3, 25 | 4, 26 | 5 27 | ], 28 | "length": 10837790, 29 | "session": { 30 | "foo": "bar" 31 | } 32 | }, 33 | "entries": [ 34 | { 35 | "type": "message", 36 | "data": { 37 | "message": "This is an example python exception" 38 | } 39 | }, 40 | { 41 | "type": "stacktrace", 42 | "data": { 43 | "frames": [ 44 | { 45 | "function": "build_msg", 46 | "instructionOffset": null, 47 | "errors": null, 48 | "colNo": null, 49 | "module": "raven.base", 50 | "package": null, 51 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 52 | "inApp": false, 53 | "instructionAddr": null, 54 | "filename": "raven/base.py", 55 | "platform": null, 56 | "vars": { 57 | "'frames'": "", 58 | "'culprit'": null, 59 | "'event_type'": "'raven.events.Message'", 60 | "'date'": "datetime.datetime(2013, 8, 13, 3, 8, 24, 880386)", 61 | "'extra'": { 62 | "'go_deeper'": [ 63 | [ 64 | { 65 | "'bar'": "'\\'\\\\\\'\\\\\\\\\\\\\\'[\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"]\\\\\\\\\\\\\\'\\\\\\'\\''", 66 | "'foo'": "'\\'\\\\\\'\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\'\\\\\\'\\''" 67 | } 68 | ] 69 | ], 70 | "'user'": "'dcramer'", 71 | "'loadavg'": [ 72 | 0.37255859375, 73 | 0.5341796875, 74 | 0.62939453125 75 | ] 76 | }, 77 | "'v'": { 78 | "'message'": "u'This is a test message generated using ``raven test``'", 79 | "'params'": [] 80 | }, 81 | "'kwargs'": { 82 | "'message'": "'This is a test message generated using ``raven test``'", 83 | "'level'": 20 84 | }, 85 | "'event_id'": "'54a322436e1b47b88e239b78998ae742'", 86 | "'tags'": null, 87 | "'data'": { 88 | "'sentry.interfaces.Message'": { 89 | "'message'": "u'This is a test message generated using ``raven test``'", 90 | "'params'": [] 91 | }, 92 | "'message'": "u'This is a test message generated using ``raven test``'" 93 | }, 94 | "'self'": "", 95 | "'time_spent'": null, 96 | "'result'": { 97 | "'sentry.interfaces.Message'": { 98 | "'message'": "u'This is a test message generated using ``raven test``'", 99 | "'params'": [] 100 | }, 101 | "'message'": "u'This is a test message generated using ``raven test``'" 102 | }, 103 | "'stack'": true, 104 | "'handler'": "", 105 | "'k'": "'sentry.interfaces.Message'", 106 | "'public_key'": null 107 | }, 108 | "lineNo": 303, 109 | "context": [ 110 | [ 111 | 298, 112 | " frames = stack" 113 | ], 114 | [ 115 | 299, 116 | "" 117 | ], 118 | [ 119 | 300, 120 | " data.update({" 121 | ], 122 | [ 123 | 301, 124 | " 'sentry.interfaces.Stacktrace': {" 125 | ], 126 | [ 127 | 302, 128 | " 'frames': get_stack_info(frames," 129 | ], 130 | [ 131 | 303, 132 | " transformer=self.transform)" 133 | ], 134 | [ 135 | 304, 136 | " }," 137 | ], 138 | [ 139 | 305, 140 | " })" 141 | ], 142 | [ 143 | 306, 144 | "" 145 | ], 146 | [ 147 | 307, 148 | " if 'sentry.interfaces.Stacktrace' in data:" 149 | ], 150 | [ 151 | 308, 152 | " if self.include_paths:" 153 | ] 154 | ], 155 | "symbolAddr": null 156 | }, 157 | { 158 | "function": "capture", 159 | "instructionOffset": null, 160 | "errors": null, 161 | "colNo": null, 162 | "module": "raven.base", 163 | "package": null, 164 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 165 | "inApp": false, 166 | "instructionAddr": null, 167 | "filename": "raven/base.py", 168 | "platform": null, 169 | "vars": { 170 | "'event_type'": "'raven.events.Message'", 171 | "'date'": null, 172 | "'extra'": { 173 | "'go_deeper'": [ 174 | [ 175 | { 176 | "'bar'": "'\\'\\\\\\'\\\\\\\\\\\\\\'[\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"]\\\\\\\\\\\\\\'\\\\\\'\\''", 177 | "'foo'": "'\\'\\\\\\'\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\'\\\\\\'\\''" 178 | } 179 | ] 180 | ], 181 | "'user'": "'dcramer'", 182 | "'loadavg'": [ 183 | 0.37255859375, 184 | 0.5341796875, 185 | 0.62939453125 186 | ] 187 | }, 188 | "'stack'": true, 189 | "'tags'": null, 190 | "'data'": null, 191 | "'self'": "", 192 | "'time_spent'": null, 193 | "'kwargs'": { 194 | "'message'": "'This is a test message generated using ``raven test``'", 195 | "'level'": 20 196 | } 197 | }, 198 | "lineNo": 459, 199 | "context": [ 200 | [ 201 | 454, 202 | " if not self.is_enabled():" 203 | ], 204 | [ 205 | 455, 206 | " return" 207 | ], 208 | [ 209 | 456, 210 | "" 211 | ], 212 | [ 213 | 457, 214 | " data = self.build_msg(" 215 | ], 216 | [ 217 | 458, 218 | " event_type, data, date, time_spent, extra, stack, tags=tags," 219 | ], 220 | [ 221 | 459, 222 | " **kwargs)" 223 | ], 224 | [ 225 | 460, 226 | "" 227 | ], 228 | [ 229 | 461, 230 | " self.send(**data)" 231 | ], 232 | [ 233 | 462, 234 | "" 235 | ], 236 | [ 237 | 463, 238 | " return (data.get('event_id'),)" 239 | ], 240 | [ 241 | 464, 242 | "" 243 | ] 244 | ], 245 | "symbolAddr": null 246 | }, 247 | { 248 | "function": "captureMessage", 249 | "instructionOffset": null, 250 | "errors": null, 251 | "colNo": null, 252 | "module": "raven.base", 253 | "package": null, 254 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 255 | "inApp": false, 256 | "instructionAddr": null, 257 | "filename": "raven/base.py", 258 | "platform": null, 259 | "vars": { 260 | "'message'": "'This is a test message generated using ``raven test``'", 261 | "'kwargs'": { 262 | "'extra'": { 263 | "'go_deeper'": [ 264 | [ 265 | "'\\'\\\\\\'\\\\\\\\\\\\\\'{\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\": [\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"], \"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'foo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\": \"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"}\\\\\\\\\\\\\\'\\\\\\'\\''" 266 | ] 267 | ], 268 | "'user'": "'dcramer'", 269 | "'loadavg'": [ 270 | 0.37255859375, 271 | 0.5341796875, 272 | 0.62939453125 273 | ] 274 | }, 275 | "'tags'": null, 276 | "'data'": null, 277 | "'level'": 20, 278 | "'stack'": true 279 | }, 280 | "'self'": "" 281 | }, 282 | "lineNo": 577, 283 | "context": [ 284 | [ 285 | 572, 286 | " \"\"\"" 287 | ], 288 | [ 289 | 573, 290 | " Creates an event from ``message``." 291 | ], 292 | [ 293 | 574, 294 | "" 295 | ], 296 | [ 297 | 575, 298 | " >>> client.captureMessage('My event just happened!')" 299 | ], 300 | [ 301 | 576, 302 | " \"\"\"" 303 | ], 304 | [ 305 | 577, 306 | " return self.capture('raven.events.Message', message=message, **kwargs)" 307 | ], 308 | [ 309 | 578, 310 | "" 311 | ], 312 | [ 313 | 579, 314 | " def captureException(self, exc_info=None, **kwargs):" 315 | ], 316 | [ 317 | 580, 318 | " \"\"\"" 319 | ], 320 | [ 321 | 581, 322 | " Creates an event from an exception." 323 | ], 324 | [ 325 | 582, 326 | "" 327 | ] 328 | ], 329 | "symbolAddr": null 330 | }, 331 | { 332 | "function": "send_test_message", 333 | "instructionOffset": null, 334 | "errors": null, 335 | "colNo": null, 336 | "module": "raven.scripts.runner", 337 | "package": null, 338 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/scripts/runner.py", 339 | "inApp": false, 340 | "instructionAddr": null, 341 | "filename": "raven/scripts/runner.py", 342 | "platform": null, 343 | "vars": { 344 | "'client'": "", 345 | "'options'": { 346 | "'tags'": null, 347 | "'data'": null 348 | }, 349 | "'data'": null, 350 | "'k'": "'secret_key'" 351 | }, 352 | "lineNo": 77, 353 | "context": [ 354 | [ 355 | 72, 356 | " level=logging.INFO," 357 | ], 358 | [ 359 | 73, 360 | " stack=True," 361 | ], 362 | [ 363 | 74, 364 | " tags=options.get('tags', {})," 365 | ], 366 | [ 367 | 75, 368 | " extra={" 369 | ], 370 | [ 371 | 76, 372 | " 'user': get_uid()," 373 | ], 374 | [ 375 | 77, 376 | " 'loadavg': get_loadavg()," 377 | ], 378 | [ 379 | 78, 380 | " }," 381 | ], 382 | [ 383 | 79, 384 | " ))" 385 | ], 386 | [ 387 | 80, 388 | "" 389 | ], 390 | [ 391 | 81, 392 | " if client.state.did_fail():" 393 | ], 394 | [ 395 | 82, 396 | " print('error!')" 397 | ] 398 | ], 399 | "symbolAddr": null 400 | }, 401 | { 402 | "function": "main", 403 | "instructionOffset": null, 404 | "errors": null, 405 | "colNo": null, 406 | "module": "raven.scripts.runner", 407 | "package": null, 408 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/scripts/runner.py", 409 | "inApp": false, 410 | "instructionAddr": null, 411 | "filename": "raven/scripts/runner.py", 412 | "platform": null, 413 | "vars": { 414 | "'root'": "", 415 | "'parser'": "", 416 | "'dsn'": "'https://ebc35f33e151401f9deac549978bda11:f3403f81e12e4c24942d505f086b2cad@app.getsentry.com/1'", 417 | "'opts'": "", 418 | "'client'": "", 419 | "'args'": [ 420 | "'test'", 421 | "'https://ebc35f33e151401f9deac549978bda11:f3403f81e12e4c24942d505f086b2cad@app.getsentry.com/1'" 422 | ] 423 | }, 424 | "lineNo": 112, 425 | "context": [ 426 | [ 427 | 107, 428 | " print(\"Using DSN configuration:\")" 429 | ], 430 | [ 431 | 108, 432 | " print(\" \", dsn)" 433 | ], 434 | [ 435 | 109, 436 | " print()" 437 | ], 438 | [ 439 | 110, 440 | "" 441 | ], 442 | [ 443 | 111, 444 | " client = Client(dsn, include_paths=['raven'])" 445 | ], 446 | [ 447 | 112, 448 | " send_test_message(client, opts.__dict__)" 449 | ] 450 | ], 451 | "symbolAddr": null 452 | } 453 | ], 454 | "framesOmitted": null, 455 | "hasSystemFrames": false 456 | } 457 | }, 458 | { 459 | "type": "template", 460 | "data": { 461 | "lineNo": 14, 462 | "context": [ 463 | [ 464 | 11, 465 | "{% endif %}\n" 466 | ], 467 | [ 468 | 12, 469 | "\n" 470 | ], 471 | [ 472 | 13, 473 | "\n" 482 | ], 483 | [ 484 | 16, 485 | "\t\n" 486 | ], 487 | [ 488 | 17, 489 | "\t\t\n" 490 | ] 491 | ], 492 | "filename": "debug_toolbar/base.html" 493 | } 494 | }, 495 | { 496 | "type": "request", 497 | "data": { 498 | "cookies": [ 499 | [ 500 | "foo", 501 | "bar" 502 | ], 503 | [ 504 | "biz", 505 | "baz" 506 | ] 507 | ], 508 | "fragment": "", 509 | "headers": [ 510 | [ 511 | "Content-Type", 512 | "application/json" 513 | ], 514 | [ 515 | "Referer", 516 | "http://example.com" 517 | ], 518 | [ 519 | "User-Agent", 520 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.72 Safari/537.36" 521 | ] 522 | ], 523 | "url": "http://example.com/foo", 524 | "env": { 525 | "ENV": "prod" 526 | }, 527 | "query": "foo=bar", 528 | "data": "{\"hello\": \"world\"}", 529 | "method": "GET" 530 | } 531 | } 532 | ], 533 | "id": "1", 534 | "message": "This is an example python exception", 535 | "packages": { 536 | "my.package": "1.0.0" 537 | }, 538 | "type": "default", 539 | "groupID": 1, 540 | "tags": [ 541 | { 542 | "value": "Chrome 28.0", 543 | "key": "browser" 544 | }, 545 | { 546 | "value": "Other", 547 | "key": "device" 548 | }, 549 | { 550 | "value": "production", 551 | "key": "environment" 552 | }, 553 | { 554 | "value": "error", 555 | "key": "level" 556 | }, 557 | { 558 | "value": "Windows 8", 559 | "key": "os" 560 | }, 561 | { 562 | "value": "7a7b2005c80710928dde1104db37ca0cdf9538fa", 563 | "key": "release" 564 | }, 565 | { 566 | "value": "http://example.com/foo", 567 | "key": "url" 568 | }, 569 | { 570 | "value": "id:1671", 571 | "key": "user" 572 | } 573 | ], 574 | "metadata": { 575 | "title": "This is an example python exception" 576 | } 577 | } 578 | ] -------------------------------------------------------------------------------- /spec/fixtures/issue_hashes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "c4a4d06bc314205bb3b6bdb612dde7f1" 4 | } 5 | ] -------------------------------------------------------------------------------- /spec/fixtures/latest_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventID": "03fc4d526a3d47c7b2219ca1f3c81cf7", 3 | "sdk": null, 4 | "errors": [], 5 | "platform": "python", 6 | "contexts": {}, 7 | "size": 7911, 8 | "dateCreated": "2016-08-30T01:11:03Z", 9 | "dateReceived": "2016-08-30T01:11:03Z", 10 | "user": { 11 | "username": "getsentry", 12 | "email": "foo@example.com", 13 | "id": "1671" 14 | }, 15 | "context": { 16 | "emptyList": [], 17 | "unauthorized": false, 18 | "emptyMap": {}, 19 | "url": "http://example.org/foo/bar/", 20 | "results": [ 21 | 1, 22 | 2, 23 | 3, 24 | 4, 25 | 5 26 | ], 27 | "length": 10837790, 28 | "session": { 29 | "foo": "bar" 30 | } 31 | }, 32 | "entries": [ 33 | { 34 | "type": "message", 35 | "data": { 36 | "message": "This is an example python exception" 37 | } 38 | }, 39 | { 40 | "type": "stacktrace", 41 | "data": { 42 | "frames": [ 43 | { 44 | "function": "build_msg", 45 | "instructionOffset": null, 46 | "errors": null, 47 | "colNo": null, 48 | "module": "raven.base", 49 | "package": null, 50 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 51 | "inApp": false, 52 | "instructionAddr": null, 53 | "filename": "raven/base.py", 54 | "platform": null, 55 | "vars": { 56 | "'frames'": "", 57 | "'culprit'": null, 58 | "'event_type'": "'raven.events.Message'", 59 | "'date'": "datetime.datetime(2013, 8, 13, 3, 8, 24, 880386)", 60 | "'extra'": { 61 | "'go_deeper'": [ 62 | [ 63 | { 64 | "'bar'": "'\\'\\\\\\'\\\\\\\\\\\\\\'[\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"]\\\\\\\\\\\\\\'\\\\\\'\\''", 65 | "'foo'": "'\\'\\\\\\'\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\'\\\\\\'\\''" 66 | } 67 | ] 68 | ], 69 | "'user'": "'dcramer'", 70 | "'loadavg'": [ 71 | 0.37255859375, 72 | 0.5341796875, 73 | 0.62939453125 74 | ] 75 | }, 76 | "'v'": { 77 | "'message'": "u'This is a test message generated using ``raven test``'", 78 | "'params'": [] 79 | }, 80 | "'kwargs'": { 81 | "'message'": "'This is a test message generated using ``raven test``'", 82 | "'level'": 20 83 | }, 84 | "'event_id'": "'54a322436e1b47b88e239b78998ae742'", 85 | "'tags'": null, 86 | "'data'": { 87 | "'sentry.interfaces.Message'": { 88 | "'message'": "u'This is a test message generated using ``raven test``'", 89 | "'params'": [] 90 | }, 91 | "'message'": "u'This is a test message generated using ``raven test``'" 92 | }, 93 | "'self'": "", 94 | "'time_spent'": null, 95 | "'result'": { 96 | "'sentry.interfaces.Message'": { 97 | "'message'": "u'This is a test message generated using ``raven test``'", 98 | "'params'": [] 99 | }, 100 | "'message'": "u'This is a test message generated using ``raven test``'" 101 | }, 102 | "'stack'": true, 103 | "'handler'": "", 104 | "'k'": "'sentry.interfaces.Message'", 105 | "'public_key'": null 106 | }, 107 | "lineNo": 303, 108 | "context": [ 109 | [ 110 | 298, 111 | " frames = stack" 112 | ], 113 | [ 114 | 299, 115 | "" 116 | ], 117 | [ 118 | 300, 119 | " data.update({" 120 | ], 121 | [ 122 | 301, 123 | " 'sentry.interfaces.Stacktrace': {" 124 | ], 125 | [ 126 | 302, 127 | " 'frames': get_stack_info(frames," 128 | ], 129 | [ 130 | 303, 131 | " transformer=self.transform)" 132 | ], 133 | [ 134 | 304, 135 | " }," 136 | ], 137 | [ 138 | 305, 139 | " })" 140 | ], 141 | [ 142 | 306, 143 | "" 144 | ], 145 | [ 146 | 307, 147 | " if 'sentry.interfaces.Stacktrace' in data:" 148 | ], 149 | [ 150 | 308, 151 | " if self.include_paths:" 152 | ] 153 | ], 154 | "symbolAddr": null 155 | }, 156 | { 157 | "function": "capture", 158 | "instructionOffset": null, 159 | "errors": null, 160 | "colNo": null, 161 | "module": "raven.base", 162 | "package": null, 163 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 164 | "inApp": false, 165 | "instructionAddr": null, 166 | "filename": "raven/base.py", 167 | "platform": null, 168 | "vars": { 169 | "'event_type'": "'raven.events.Message'", 170 | "'date'": null, 171 | "'extra'": { 172 | "'go_deeper'": [ 173 | [ 174 | { 175 | "'bar'": "'\\'\\\\\\'\\\\\\\\\\\\\\'[\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"]\\\\\\\\\\\\\\'\\\\\\'\\''", 176 | "'foo'": "'\\'\\\\\\'\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\'\\\\\\'\\''" 177 | } 178 | ] 179 | ], 180 | "'user'": "'dcramer'", 181 | "'loadavg'": [ 182 | 0.37255859375, 183 | 0.5341796875, 184 | 0.62939453125 185 | ] 186 | }, 187 | "'stack'": true, 188 | "'tags'": null, 189 | "'data'": null, 190 | "'self'": "", 191 | "'time_spent'": null, 192 | "'kwargs'": { 193 | "'message'": "'This is a test message generated using ``raven test``'", 194 | "'level'": 20 195 | } 196 | }, 197 | "lineNo": 459, 198 | "context": [ 199 | [ 200 | 454, 201 | " if not self.is_enabled():" 202 | ], 203 | [ 204 | 455, 205 | " return" 206 | ], 207 | [ 208 | 456, 209 | "" 210 | ], 211 | [ 212 | 457, 213 | " data = self.build_msg(" 214 | ], 215 | [ 216 | 458, 217 | " event_type, data, date, time_spent, extra, stack, tags=tags," 218 | ], 219 | [ 220 | 459, 221 | " **kwargs)" 222 | ], 223 | [ 224 | 460, 225 | "" 226 | ], 227 | [ 228 | 461, 229 | " self.send(**data)" 230 | ], 231 | [ 232 | 462, 233 | "" 234 | ], 235 | [ 236 | 463, 237 | " return (data.get('event_id'),)" 238 | ], 239 | [ 240 | 464, 241 | "" 242 | ] 243 | ], 244 | "symbolAddr": null 245 | }, 246 | { 247 | "function": "captureMessage", 248 | "instructionOffset": null, 249 | "errors": null, 250 | "colNo": null, 251 | "module": "raven.base", 252 | "package": null, 253 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 254 | "inApp": false, 255 | "instructionAddr": null, 256 | "filename": "raven/base.py", 257 | "platform": null, 258 | "vars": { 259 | "'message'": "'This is a test message generated using ``raven test``'", 260 | "'kwargs'": { 261 | "'extra'": { 262 | "'go_deeper'": [ 263 | [ 264 | "'\\'\\\\\\'\\\\\\\\\\\\\\'{\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\": [\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"], \"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'foo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\": \"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"}\\\\\\\\\\\\\\'\\\\\\'\\''" 265 | ] 266 | ], 267 | "'user'": "'dcramer'", 268 | "'loadavg'": [ 269 | 0.37255859375, 270 | 0.5341796875, 271 | 0.62939453125 272 | ] 273 | }, 274 | "'tags'": null, 275 | "'data'": null, 276 | "'level'": 20, 277 | "'stack'": true 278 | }, 279 | "'self'": "" 280 | }, 281 | "lineNo": 577, 282 | "context": [ 283 | [ 284 | 572, 285 | " \"\"\"" 286 | ], 287 | [ 288 | 573, 289 | " Creates an event from ``message``." 290 | ], 291 | [ 292 | 574, 293 | "" 294 | ], 295 | [ 296 | 575, 297 | " >>> client.captureMessage('My event just happened!')" 298 | ], 299 | [ 300 | 576, 301 | " \"\"\"" 302 | ], 303 | [ 304 | 577, 305 | " return self.capture('raven.events.Message', message=message, **kwargs)" 306 | ], 307 | [ 308 | 578, 309 | "" 310 | ], 311 | [ 312 | 579, 313 | " def captureException(self, exc_info=None, **kwargs):" 314 | ], 315 | [ 316 | 580, 317 | " \"\"\"" 318 | ], 319 | [ 320 | 581, 321 | " Creates an event from an exception." 322 | ], 323 | [ 324 | 582, 325 | "" 326 | ] 327 | ], 328 | "symbolAddr": null 329 | }, 330 | { 331 | "function": "send_test_message", 332 | "instructionOffset": null, 333 | "errors": null, 334 | "colNo": null, 335 | "module": "raven.scripts.runner", 336 | "package": null, 337 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/scripts/runner.py", 338 | "inApp": false, 339 | "instructionAddr": null, 340 | "filename": "raven/scripts/runner.py", 341 | "platform": null, 342 | "vars": { 343 | "'client'": "", 344 | "'options'": { 345 | "'tags'": null, 346 | "'data'": null 347 | }, 348 | "'data'": null, 349 | "'k'": "'secret_key'" 350 | }, 351 | "lineNo": 77, 352 | "context": [ 353 | [ 354 | 72, 355 | " level=logging.INFO," 356 | ], 357 | [ 358 | 73, 359 | " stack=True," 360 | ], 361 | [ 362 | 74, 363 | " tags=options.get('tags', {})," 364 | ], 365 | [ 366 | 75, 367 | " extra={" 368 | ], 369 | [ 370 | 76, 371 | " 'user': get_uid()," 372 | ], 373 | [ 374 | 77, 375 | " 'loadavg': get_loadavg()," 376 | ], 377 | [ 378 | 78, 379 | " }," 380 | ], 381 | [ 382 | 79, 383 | " ))" 384 | ], 385 | [ 386 | 80, 387 | "" 388 | ], 389 | [ 390 | 81, 391 | " if client.state.did_fail():" 392 | ], 393 | [ 394 | 82, 395 | " print('error!')" 396 | ] 397 | ], 398 | "symbolAddr": null 399 | }, 400 | { 401 | "function": "main", 402 | "instructionOffset": null, 403 | "errors": null, 404 | "colNo": null, 405 | "module": "raven.scripts.runner", 406 | "package": null, 407 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/scripts/runner.py", 408 | "inApp": false, 409 | "instructionAddr": null, 410 | "filename": "raven/scripts/runner.py", 411 | "platform": null, 412 | "vars": { 413 | "'root'": "", 414 | "'parser'": "", 415 | "'dsn'": "'https://ebc35f33e151401f9deac549978bda11:f3403f81e12e4c24942d505f086b2cad@app.getsentry.com/1'", 416 | "'opts'": "", 417 | "'client'": "", 418 | "'args'": [ 419 | "'test'", 420 | "'https://ebc35f33e151401f9deac549978bda11:f3403f81e12e4c24942d505f086b2cad@app.getsentry.com/1'" 421 | ] 422 | }, 423 | "lineNo": 112, 424 | "context": [ 425 | [ 426 | 107, 427 | " print(\"Using DSN configuration:\")" 428 | ], 429 | [ 430 | 108, 431 | " print(\" \", dsn)" 432 | ], 433 | [ 434 | 109, 435 | " print()" 436 | ], 437 | [ 438 | 110, 439 | "" 440 | ], 441 | [ 442 | 111, 443 | " client = Client(dsn, include_paths=['raven'])" 444 | ], 445 | [ 446 | 112, 447 | " send_test_message(client, opts.__dict__)" 448 | ] 449 | ], 450 | "symbolAddr": null 451 | } 452 | ], 453 | "framesOmitted": null, 454 | "hasSystemFrames": false 455 | } 456 | }, 457 | { 458 | "type": "template", 459 | "data": { 460 | "lineNo": 14, 461 | "context": [ 462 | [ 463 | 11, 464 | "{% endif %}\n" 465 | ], 466 | [ 467 | 12, 468 | "\n" 469 | ], 470 | [ 471 | 13, 472 | "\n" 481 | ], 482 | [ 483 | 16, 484 | "\t\n" 485 | ], 486 | [ 487 | 17, 488 | "\t\t\n" 489 | ] 490 | ], 491 | "filename": "debug_toolbar/base.html" 492 | } 493 | }, 494 | { 495 | "type": "request", 496 | "data": { 497 | "cookies": [ 498 | [ 499 | "foo", 500 | "bar" 501 | ], 502 | [ 503 | "biz", 504 | "baz" 505 | ] 506 | ], 507 | "fragment": "", 508 | "headers": [ 509 | [ 510 | "Content-Type", 511 | "application/json" 512 | ], 513 | [ 514 | "Referer", 515 | "http://example.com" 516 | ], 517 | [ 518 | "User-Agent", 519 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.72 Safari/537.36" 520 | ] 521 | ], 522 | "url": "http://example.com/foo", 523 | "env": { 524 | "ENV": "prod" 525 | }, 526 | "query": "foo=bar", 527 | "data": "{\"hello\": \"world\"}", 528 | "method": "GET" 529 | } 530 | } 531 | ], 532 | "id": "1", 533 | "message": "This is an example python exception", 534 | "packages": { 535 | "my.package": "1.0.0" 536 | }, 537 | "type": "default", 538 | "groupID": 1, 539 | "tags": [ 540 | { 541 | "value": "Chrome 28.0", 542 | "key": "browser" 543 | }, 544 | { 545 | "value": "Other", 546 | "key": "device" 547 | }, 548 | { 549 | "value": "production", 550 | "key": "environment" 551 | }, 552 | { 553 | "value": "error", 554 | "key": "level" 555 | }, 556 | { 557 | "value": "Windows 8", 558 | "key": "os" 559 | }, 560 | { 561 | "value": "7a7b2005c80710928dde1104db37ca0cdf9538fa", 562 | "key": "release" 563 | }, 564 | { 565 | "value": "http://example.com/foo", 566 | "key": "url" 567 | }, 568 | { 569 | "value": "id:1671", 570 | "key": "user" 571 | } 572 | ], 573 | "metadata": { 574 | "title": "This is an example python exception" 575 | } 576 | } -------------------------------------------------------------------------------- /spec/fixtures/oldest_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventID": "03fc4d526a3d47c7b2219ca1f3c81cf7", 3 | "sdk": null, 4 | "errors": [], 5 | "platform": "python", 6 | "contexts": {}, 7 | "size": 7911, 8 | "dateCreated": "2016-08-30T01:11:03Z", 9 | "dateReceived": "2016-08-30T01:11:03Z", 10 | "user": { 11 | "username": "getsentry", 12 | "email": "foo@example.com", 13 | "id": "1671" 14 | }, 15 | "context": { 16 | "emptyList": [], 17 | "unauthorized": false, 18 | "emptyMap": {}, 19 | "url": "http://example.org/foo/bar/", 20 | "results": [ 21 | 1, 22 | 2, 23 | 3, 24 | 4, 25 | 5 26 | ], 27 | "length": 10837790, 28 | "session": { 29 | "foo": "bar" 30 | } 31 | }, 32 | "entries": [ 33 | { 34 | "type": "message", 35 | "data": { 36 | "message": "This is an example python exception" 37 | } 38 | }, 39 | { 40 | "type": "stacktrace", 41 | "data": { 42 | "frames": [ 43 | { 44 | "function": "build_msg", 45 | "instructionOffset": null, 46 | "errors": null, 47 | "colNo": null, 48 | "module": "raven.base", 49 | "package": null, 50 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 51 | "inApp": false, 52 | "instructionAddr": null, 53 | "filename": "raven/base.py", 54 | "platform": null, 55 | "vars": { 56 | "'frames'": "", 57 | "'culprit'": null, 58 | "'event_type'": "'raven.events.Message'", 59 | "'date'": "datetime.datetime(2013, 8, 13, 3, 8, 24, 880386)", 60 | "'extra'": { 61 | "'go_deeper'": [ 62 | [ 63 | { 64 | "'bar'": "'\\'\\\\\\'\\\\\\\\\\\\\\'[\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"]\\\\\\\\\\\\\\'\\\\\\'\\''", 65 | "'foo'": "'\\'\\\\\\'\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\'\\\\\\'\\''" 66 | } 67 | ] 68 | ], 69 | "'user'": "'dcramer'", 70 | "'loadavg'": [ 71 | 0.37255859375, 72 | 0.5341796875, 73 | 0.62939453125 74 | ] 75 | }, 76 | "'v'": { 77 | "'message'": "u'This is a test message generated using ``raven test``'", 78 | "'params'": [] 79 | }, 80 | "'kwargs'": { 81 | "'message'": "'This is a test message generated using ``raven test``'", 82 | "'level'": 20 83 | }, 84 | "'event_id'": "'54a322436e1b47b88e239b78998ae742'", 85 | "'tags'": null, 86 | "'data'": { 87 | "'sentry.interfaces.Message'": { 88 | "'message'": "u'This is a test message generated using ``raven test``'", 89 | "'params'": [] 90 | }, 91 | "'message'": "u'This is a test message generated using ``raven test``'" 92 | }, 93 | "'self'": "", 94 | "'time_spent'": null, 95 | "'result'": { 96 | "'sentry.interfaces.Message'": { 97 | "'message'": "u'This is a test message generated using ``raven test``'", 98 | "'params'": [] 99 | }, 100 | "'message'": "u'This is a test message generated using ``raven test``'" 101 | }, 102 | "'stack'": true, 103 | "'handler'": "", 104 | "'k'": "'sentry.interfaces.Message'", 105 | "'public_key'": null 106 | }, 107 | "lineNo": 303, 108 | "context": [ 109 | [ 110 | 298, 111 | " frames = stack" 112 | ], 113 | [ 114 | 299, 115 | "" 116 | ], 117 | [ 118 | 300, 119 | " data.update({" 120 | ], 121 | [ 122 | 301, 123 | " 'sentry.interfaces.Stacktrace': {" 124 | ], 125 | [ 126 | 302, 127 | " 'frames': get_stack_info(frames," 128 | ], 129 | [ 130 | 303, 131 | " transformer=self.transform)" 132 | ], 133 | [ 134 | 304, 135 | " }," 136 | ], 137 | [ 138 | 305, 139 | " })" 140 | ], 141 | [ 142 | 306, 143 | "" 144 | ], 145 | [ 146 | 307, 147 | " if 'sentry.interfaces.Stacktrace' in data:" 148 | ], 149 | [ 150 | 308, 151 | " if self.include_paths:" 152 | ] 153 | ], 154 | "symbolAddr": null 155 | }, 156 | { 157 | "function": "capture", 158 | "instructionOffset": null, 159 | "errors": null, 160 | "colNo": null, 161 | "module": "raven.base", 162 | "package": null, 163 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 164 | "inApp": false, 165 | "instructionAddr": null, 166 | "filename": "raven/base.py", 167 | "platform": null, 168 | "vars": { 169 | "'event_type'": "'raven.events.Message'", 170 | "'date'": null, 171 | "'extra'": { 172 | "'go_deeper'": [ 173 | [ 174 | { 175 | "'bar'": "'\\'\\\\\\'\\\\\\\\\\\\\\'[\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"]\\\\\\\\\\\\\\'\\\\\\'\\''", 176 | "'foo'": "'\\'\\\\\\'\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\'\\\\\\'\\''" 177 | } 178 | ] 179 | ], 180 | "'user'": "'dcramer'", 181 | "'loadavg'": [ 182 | 0.37255859375, 183 | 0.5341796875, 184 | 0.62939453125 185 | ] 186 | }, 187 | "'stack'": true, 188 | "'tags'": null, 189 | "'data'": null, 190 | "'self'": "", 191 | "'time_spent'": null, 192 | "'kwargs'": { 193 | "'message'": "'This is a test message generated using ``raven test``'", 194 | "'level'": 20 195 | } 196 | }, 197 | "lineNo": 459, 198 | "context": [ 199 | [ 200 | 454, 201 | " if not self.is_enabled():" 202 | ], 203 | [ 204 | 455, 205 | " return" 206 | ], 207 | [ 208 | 456, 209 | "" 210 | ], 211 | [ 212 | 457, 213 | " data = self.build_msg(" 214 | ], 215 | [ 216 | 458, 217 | " event_type, data, date, time_spent, extra, stack, tags=tags," 218 | ], 219 | [ 220 | 459, 221 | " **kwargs)" 222 | ], 223 | [ 224 | 460, 225 | "" 226 | ], 227 | [ 228 | 461, 229 | " self.send(**data)" 230 | ], 231 | [ 232 | 462, 233 | "" 234 | ], 235 | [ 236 | 463, 237 | " return (data.get('event_id'),)" 238 | ], 239 | [ 240 | 464, 241 | "" 242 | ] 243 | ], 244 | "symbolAddr": null 245 | }, 246 | { 247 | "function": "captureMessage", 248 | "instructionOffset": null, 249 | "errors": null, 250 | "colNo": null, 251 | "module": "raven.base", 252 | "package": null, 253 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 254 | "inApp": false, 255 | "instructionAddr": null, 256 | "filename": "raven/base.py", 257 | "platform": null, 258 | "vars": { 259 | "'message'": "'This is a test message generated using ``raven test``'", 260 | "'kwargs'": { 261 | "'extra'": { 262 | "'go_deeper'": [ 263 | [ 264 | "'\\'\\\\\\'\\\\\\\\\\\\\\'{\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\": [\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"], \"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'foo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\": \"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"}\\\\\\\\\\\\\\'\\\\\\'\\''" 265 | ] 266 | ], 267 | "'user'": "'dcramer'", 268 | "'loadavg'": [ 269 | 0.37255859375, 270 | 0.5341796875, 271 | 0.62939453125 272 | ] 273 | }, 274 | "'tags'": null, 275 | "'data'": null, 276 | "'level'": 20, 277 | "'stack'": true 278 | }, 279 | "'self'": "" 280 | }, 281 | "lineNo": 577, 282 | "context": [ 283 | [ 284 | 572, 285 | " \"\"\"" 286 | ], 287 | [ 288 | 573, 289 | " Creates an event from ``message``." 290 | ], 291 | [ 292 | 574, 293 | "" 294 | ], 295 | [ 296 | 575, 297 | " >>> client.captureMessage('My event just happened!')" 298 | ], 299 | [ 300 | 576, 301 | " \"\"\"" 302 | ], 303 | [ 304 | 577, 305 | " return self.capture('raven.events.Message', message=message, **kwargs)" 306 | ], 307 | [ 308 | 578, 309 | "" 310 | ], 311 | [ 312 | 579, 313 | " def captureException(self, exc_info=None, **kwargs):" 314 | ], 315 | [ 316 | 580, 317 | " \"\"\"" 318 | ], 319 | [ 320 | 581, 321 | " Creates an event from an exception." 322 | ], 323 | [ 324 | 582, 325 | "" 326 | ] 327 | ], 328 | "symbolAddr": null 329 | }, 330 | { 331 | "function": "send_test_message", 332 | "instructionOffset": null, 333 | "errors": null, 334 | "colNo": null, 335 | "module": "raven.scripts.runner", 336 | "package": null, 337 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/scripts/runner.py", 338 | "inApp": false, 339 | "instructionAddr": null, 340 | "filename": "raven/scripts/runner.py", 341 | "platform": null, 342 | "vars": { 343 | "'client'": "", 344 | "'options'": { 345 | "'tags'": null, 346 | "'data'": null 347 | }, 348 | "'data'": null, 349 | "'k'": "'secret_key'" 350 | }, 351 | "lineNo": 77, 352 | "context": [ 353 | [ 354 | 72, 355 | " level=logging.INFO," 356 | ], 357 | [ 358 | 73, 359 | " stack=True," 360 | ], 361 | [ 362 | 74, 363 | " tags=options.get('tags', {})," 364 | ], 365 | [ 366 | 75, 367 | " extra={" 368 | ], 369 | [ 370 | 76, 371 | " 'user': get_uid()," 372 | ], 373 | [ 374 | 77, 375 | " 'loadavg': get_loadavg()," 376 | ], 377 | [ 378 | 78, 379 | " }," 380 | ], 381 | [ 382 | 79, 383 | " ))" 384 | ], 385 | [ 386 | 80, 387 | "" 388 | ], 389 | [ 390 | 81, 391 | " if client.state.did_fail():" 392 | ], 393 | [ 394 | 82, 395 | " print('error!')" 396 | ] 397 | ], 398 | "symbolAddr": null 399 | }, 400 | { 401 | "function": "main", 402 | "instructionOffset": null, 403 | "errors": null, 404 | "colNo": null, 405 | "module": "raven.scripts.runner", 406 | "package": null, 407 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/scripts/runner.py", 408 | "inApp": false, 409 | "instructionAddr": null, 410 | "filename": "raven/scripts/runner.py", 411 | "platform": null, 412 | "vars": { 413 | "'root'": "", 414 | "'parser'": "", 415 | "'dsn'": "'https://ebc35f33e151401f9deac549978bda11:f3403f81e12e4c24942d505f086b2cad@app.getsentry.com/1'", 416 | "'opts'": "", 417 | "'client'": "", 418 | "'args'": [ 419 | "'test'", 420 | "'https://ebc35f33e151401f9deac549978bda11:f3403f81e12e4c24942d505f086b2cad@app.getsentry.com/1'" 421 | ] 422 | }, 423 | "lineNo": 112, 424 | "context": [ 425 | [ 426 | 107, 427 | " print(\"Using DSN configuration:\")" 428 | ], 429 | [ 430 | 108, 431 | " print(\" \", dsn)" 432 | ], 433 | [ 434 | 109, 435 | " print()" 436 | ], 437 | [ 438 | 110, 439 | "" 440 | ], 441 | [ 442 | 111, 443 | " client = Client(dsn, include_paths=['raven'])" 444 | ], 445 | [ 446 | 112, 447 | " send_test_message(client, opts.__dict__)" 448 | ] 449 | ], 450 | "symbolAddr": null 451 | } 452 | ], 453 | "framesOmitted": null, 454 | "hasSystemFrames": false 455 | } 456 | }, 457 | { 458 | "type": "template", 459 | "data": { 460 | "lineNo": 14, 461 | "context": [ 462 | [ 463 | 11, 464 | "{% endif %}\n" 465 | ], 466 | [ 467 | 12, 468 | "\n" 469 | ], 470 | [ 471 | 13, 472 | "\n" 481 | ], 482 | [ 483 | 16, 484 | "\t\n" 485 | ], 486 | [ 487 | 17, 488 | "\t\t\n" 489 | ] 490 | ], 491 | "filename": "debug_toolbar/base.html" 492 | } 493 | }, 494 | { 495 | "type": "request", 496 | "data": { 497 | "cookies": [ 498 | [ 499 | "foo", 500 | "bar" 501 | ], 502 | [ 503 | "biz", 504 | "baz" 505 | ] 506 | ], 507 | "fragment": "", 508 | "headers": [ 509 | [ 510 | "Content-Type", 511 | "application/json" 512 | ], 513 | [ 514 | "Referer", 515 | "http://example.com" 516 | ], 517 | [ 518 | "User-Agent", 519 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.72 Safari/537.36" 520 | ] 521 | ], 522 | "url": "http://example.com/foo", 523 | "env": { 524 | "ENV": "prod" 525 | }, 526 | "query": "foo=bar", 527 | "data": "{\"hello\": \"world\"}", 528 | "method": "GET" 529 | } 530 | } 531 | ], 532 | "id": "1", 533 | "message": "This is an example python exception", 534 | "packages": { 535 | "my.package": "1.0.0" 536 | }, 537 | "type": "default", 538 | "groupID": 1, 539 | "tags": [ 540 | { 541 | "value": "Chrome 28.0", 542 | "key": "browser" 543 | }, 544 | { 545 | "value": "Other", 546 | "key": "device" 547 | }, 548 | { 549 | "value": "production", 550 | "key": "environment" 551 | }, 552 | { 553 | "value": "error", 554 | "key": "level" 555 | }, 556 | { 557 | "value": "Windows 8", 558 | "key": "os" 559 | }, 560 | { 561 | "value": "7a7b2005c80710928dde1104db37ca0cdf9538fa", 562 | "key": "release" 563 | }, 564 | { 565 | "value": "http://example.com/foo", 566 | "key": "url" 567 | }, 568 | { 569 | "value": "id:1671", 570 | "key": "user" 571 | } 572 | ], 573 | "metadata": { 574 | "title": "This is an example python exception" 575 | } 576 | } -------------------------------------------------------------------------------- /spec/fixtures/organization.json: -------------------------------------------------------------------------------- 1 | { 2 | "pendingAccessRequests": 0, 3 | "slug": "the-interstellar-jurisdiction", 4 | "name": "The Interstellar Jurisdiction", 5 | "quota": { 6 | "projectLimit": 100, 7 | "maxRate": 0 8 | }, 9 | "dateCreated": "2016-08-26T20:00:53.596Z", 10 | "access": [], 11 | "teams": [ 12 | { 13 | "slug": "ancient-gabelers", 14 | "name": "Ancient Gabelers", 15 | "hasAccess": true, 16 | "isPending": false, 17 | "dateCreated": "2016-08-26T20:01:03.171Z", 18 | "isMember": false, 19 | "id": "3", 20 | "projects": [] 21 | }, 22 | { 23 | "slug": "powerful-abolitionist", 24 | "name": "Powerful Abolitionist", 25 | "hasAccess": true, 26 | "isPending": false, 27 | "dateCreated": "2016-08-26T20:00:53.607Z", 28 | "isMember": false, 29 | "id": "2", 30 | "projects": [ 31 | { 32 | "status": "active", 33 | "slug": "prime-mover", 34 | "defaultEnvironment": null, 35 | "features": [], 36 | "color": "#bf5b3f", 37 | "isPublic": false, 38 | "dateCreated": "2016-08-26T20:00:56.523Z", 39 | "platforms": [], 40 | "callSign": "PRIME-MOVER", 41 | "firstEvent": null, 42 | "isBookmarked": false, 43 | "callSignReviewed": false, 44 | "id": "3", 45 | "name": "Prime Mover" 46 | }, 47 | { 48 | "status": "active", 49 | "slug": "pump-station", 50 | "defaultEnvironment": null, 51 | "features": [], 52 | "color": "#3fbf7f", 53 | "isPublic": false, 54 | "dateCreated": "2016-08-26T20:00:53.610Z", 55 | "platforms": [], 56 | "callSign": "PUMP-STATION", 57 | "firstEvent": null, 58 | "isBookmarked": false, 59 | "callSignReviewed": false, 60 | "id": "2", 61 | "name": "Pump Station" 62 | }, 63 | { 64 | "status": "active", 65 | "slug": "the-spoiled-yoghurt", 66 | "defaultEnvironment": null, 67 | "features": [], 68 | "color": "#bf6e3f", 69 | "isPublic": false, 70 | "dateCreated": "2016-08-26T20:01:03.113Z", 71 | "platforms": [], 72 | "callSign": "THE-SPOILED-YOGHURT", 73 | "firstEvent": null, 74 | "isBookmarked": false, 75 | "callSignReviewed": false, 76 | "id": "4", 77 | "name": "The Spoiled Yoghurt" 78 | } 79 | ] 80 | } 81 | ], 82 | "onboardingTasks": [], 83 | "id": "2", 84 | "isEarlyAdopter": false, 85 | "features": [ 86 | "sso", 87 | "api-keys", 88 | "open-membership", 89 | "shared-issues" 90 | ] 91 | } -------------------------------------------------------------------------------- /spec/fixtures/organization_projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "status": "active", 4 | "slug": "prime-mover", 5 | "defaultEnvironment": null, 6 | "features": [], 7 | "color": "#bf5b3f", 8 | "isPublic": false, 9 | "dateCreated": "2016-08-26T20:00:56.523Z", 10 | "platforms": [], 11 | "callSign": "PRIME-MOVER", 12 | "firstEvent": null, 13 | "team": { 14 | "slug": "powerful-abolitionist", 15 | "name": "Powerful Abolitionist", 16 | "hasAccess": true, 17 | "isPending": false, 18 | "dateCreated": "2016-08-26T20:00:53.607Z", 19 | "isMember": false, 20 | "id": "2" 21 | }, 22 | "isBookmarked": false, 23 | "callSignReviewed": false, 24 | "id": "3", 25 | "name": "Prime Mover" 26 | }, 27 | { 28 | "status": "active", 29 | "slug": "pump-station", 30 | "defaultEnvironment": null, 31 | "features": [], 32 | "color": "#3fbf7f", 33 | "isPublic": false, 34 | "dateCreated": "2016-08-26T20:00:53.610Z", 35 | "platforms": [], 36 | "callSign": "PUMP-STATION", 37 | "firstEvent": null, 38 | "team": { 39 | "slug": "powerful-abolitionist", 40 | "name": "Powerful Abolitionist", 41 | "hasAccess": true, 42 | "isPending": false, 43 | "dateCreated": "2016-08-26T20:00:53.607Z", 44 | "isMember": false, 45 | "id": "2" 46 | }, 47 | "isBookmarked": false, 48 | "callSignReviewed": false, 49 | "id": "2", 50 | "name": "Pump Station" 51 | }, 52 | { 53 | "status": "active", 54 | "slug": "the-spoiled-yoghurt", 55 | "defaultEnvironment": null, 56 | "features": [], 57 | "color": "#bf6e3f", 58 | "isPublic": false, 59 | "dateCreated": "2016-08-26T20:01:03.113Z", 60 | "platforms": [], 61 | "callSign": "THE-SPOILED-YOGHURT", 62 | "firstEvent": null, 63 | "team": { 64 | "slug": "powerful-abolitionist", 65 | "name": "Powerful Abolitionist", 66 | "hasAccess": true, 67 | "isPending": false, 68 | "dateCreated": "2016-08-26T20:00:53.607Z", 69 | "isMember": false, 70 | "id": "2" 71 | }, 72 | "isBookmarked": false, 73 | "callSignReviewed": false, 74 | "id": "4", 75 | "name": "The Spoiled Yoghurt" 76 | } 77 | ] -------------------------------------------------------------------------------- /spec/fixtures/organization_stats.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | 1472158800, 4 | 6330 5 | ], 6 | [ 7 | 1472162400, 8 | 6517 9 | ], 10 | [ 11 | 1472166000, 12 | 7507 13 | ], 14 | [ 15 | 1472169600, 16 | 7376 17 | ], 18 | [ 19 | 1472173200, 20 | 8422 21 | ], 22 | [ 23 | 1472176800, 24 | 5237 25 | ], 26 | [ 27 | 1472180400, 28 | 6842 29 | ], 30 | [ 31 | 1472184000, 32 | 8213 33 | ], 34 | [ 35 | 1472187600, 36 | 5895 37 | ], 38 | [ 39 | 1472191200, 40 | 7470 41 | ], 42 | [ 43 | 1472194800, 44 | 10198 45 | ], 46 | [ 47 | 1472198400, 48 | 6295 49 | ], 50 | [ 51 | 1472202000, 52 | 8350 53 | ], 54 | [ 55 | 1472205600, 56 | 7621 57 | ], 58 | [ 59 | 1472209200, 60 | 6770 61 | ], 62 | [ 63 | 1472212800, 64 | 7852 65 | ], 66 | [ 67 | 1472216400, 68 | 6069 69 | ], 70 | [ 71 | 1472220000, 72 | 6586 73 | ], 74 | [ 75 | 1472223600, 76 | 8701 77 | ], 78 | [ 79 | 1472227200, 80 | 6722 81 | ], 82 | [ 83 | 1472230800, 84 | 7782 85 | ], 86 | [ 87 | 1472234400, 88 | 6347 89 | ], 90 | [ 91 | 1472238000, 92 | 6838 93 | ], 94 | [ 95 | 1472241600, 96 | 7657 97 | ] 98 | ] -------------------------------------------------------------------------------- /spec/fixtures/organizations.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "The Interstellar Jurisdiction", 4 | "slug": "the-interstellar-jurisdiction", 5 | "id": "2", 6 | "isEarlyAdopter": false, 7 | "dateCreated": "2016-08-26T20:00:53.596Z" 8 | } 9 | ] -------------------------------------------------------------------------------- /spec/fixtures/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "active", 3 | "options": { 4 | "sentry:csp_ignored_sources_defaults": true, 5 | "sentry:scrub_defaults": true, 6 | "sentry:origins": "*", 7 | "sentry:resolve_age": 0, 8 | "sentry:sensitive_fields": [], 9 | "sentry:csp_ignored_sources": "", 10 | "sentry:scrub_data": true, 11 | "feedback:branding": true, 12 | "sentry:default_environment": null 13 | }, 14 | "isPublic": false, 15 | "defaultEnvironment": null, 16 | "features": [], 17 | "color": "#3fbf7f", 18 | "slug": "pump-station", 19 | "activePlugins": [], 20 | "dateCreated": "2016-08-26T20:00:53.610Z", 21 | "platforms": [], 22 | "callSign": "PUMP-STATION", 23 | "firstEvent": null, 24 | "team": { 25 | "slug": "powerful-abolitionist", 26 | "name": "Powerful Abolitionist", 27 | "hasAccess": true, 28 | "isPending": false, 29 | "dateCreated": "2016-08-26T20:00:53.607Z", 30 | "isMember": false, 31 | "id": "2" 32 | }, 33 | "organization": { 34 | "name": "The Interstellar Jurisdiction", 35 | "slug": "the-interstellar-jurisdiction", 36 | "id": "2", 37 | "isEarlyAdopter": false, 38 | "dateCreated": "2016-08-26T20:00:53.596Z" 39 | }, 40 | "isBookmarked": false, 41 | "callSignReviewed": false, 42 | "id": "2", 43 | "name": "Pump Station" 44 | } -------------------------------------------------------------------------------- /spec/fixtures/project_dsym_files.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "headers": { 4 | "Content-Type": "application/x-mach-binary" 5 | }, 6 | "sha1": "e36f6fc011d93d2148d8a443a87fd47401c13e02", 7 | "uuid": "0a77fceb-983e-371d-867f-18116ce19a26", 8 | "objectName": "GMThirdParty", 9 | "dateCreated": "2016-08-26T09:36:04.866Z", 10 | "cpuName": "arm64", 11 | "id": "6845", 12 | "symbolType": "macho", 13 | "size": 398508 14 | } 15 | ] -------------------------------------------------------------------------------- /spec/fixtures/project_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventID": "ef4e4e732d2544279851cf7c1b42716e", 3 | "sdk": null, 4 | "errors": [], 5 | "platform": "python", 6 | "contexts": {}, 7 | "nextEventID": null, 8 | "size": 7911, 9 | "dateCreated": "2016-08-26T20:00:53Z", 10 | "dateReceived": "2016-08-26T20:00:53Z", 11 | "user": { 12 | "username": "getsentry", 13 | "email": "foo@example.com", 14 | "id": "1671" 15 | }, 16 | "context": { 17 | "emptyList": [], 18 | "unauthorized": false, 19 | "emptyMap": {}, 20 | "url": "http://example.org/foo/bar/", 21 | "results": [ 22 | 1, 23 | 2, 24 | 3, 25 | 4, 26 | 5 27 | ], 28 | "length": 10837790, 29 | "session": { 30 | "foo": "bar" 31 | } 32 | }, 33 | "entries": [ 34 | { 35 | "type": "message", 36 | "data": { 37 | "message": "This is an example python exception" 38 | } 39 | }, 40 | { 41 | "type": "stacktrace", 42 | "data": { 43 | "frames": [ 44 | { 45 | "function": "build_msg", 46 | "instructionOffset": null, 47 | "errors": null, 48 | "colNo": null, 49 | "module": "raven.base", 50 | "package": null, 51 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 52 | "inApp": false, 53 | "instructionAddr": null, 54 | "filename": "raven/base.py", 55 | "platform": null, 56 | "vars": { 57 | "'frames'": "", 58 | "'culprit'": null, 59 | "'event_type'": "'raven.events.Message'", 60 | "'date'": "datetime.datetime(2013, 8, 13, 3, 8, 24, 880386)", 61 | "'extra'": { 62 | "'go_deeper'": [ 63 | [ 64 | { 65 | "'bar'": "'\\'\\\\\\'\\\\\\\\\\\\\\'[\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"]\\\\\\\\\\\\\\'\\\\\\'\\''", 66 | "'foo'": "'\\'\\\\\\'\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\'\\\\\\'\\''" 67 | } 68 | ] 69 | ], 70 | "'user'": "'dcramer'", 71 | "'loadavg'": [ 72 | 0.37255859375, 73 | 0.5341796875, 74 | 0.62939453125 75 | ] 76 | }, 77 | "'v'": { 78 | "'message'": "u'This is a test message generated using ``raven test``'", 79 | "'params'": [] 80 | }, 81 | "'kwargs'": { 82 | "'message'": "'This is a test message generated using ``raven test``'", 83 | "'level'": 20 84 | }, 85 | "'event_id'": "'54a322436e1b47b88e239b78998ae742'", 86 | "'tags'": null, 87 | "'data'": { 88 | "'sentry.interfaces.Message'": { 89 | "'message'": "u'This is a test message generated using ``raven test``'", 90 | "'params'": [] 91 | }, 92 | "'message'": "u'This is a test message generated using ``raven test``'" 93 | }, 94 | "'self'": "", 95 | "'time_spent'": null, 96 | "'result'": { 97 | "'sentry.interfaces.Message'": { 98 | "'message'": "u'This is a test message generated using ``raven test``'", 99 | "'params'": [] 100 | }, 101 | "'message'": "u'This is a test message generated using ``raven test``'" 102 | }, 103 | "'stack'": true, 104 | "'handler'": "", 105 | "'k'": "'sentry.interfaces.Message'", 106 | "'public_key'": null 107 | }, 108 | "lineNo": 303, 109 | "context": [ 110 | [ 111 | 298, 112 | " frames = stack" 113 | ], 114 | [ 115 | 299, 116 | "" 117 | ], 118 | [ 119 | 300, 120 | " data.update({" 121 | ], 122 | [ 123 | 301, 124 | " 'sentry.interfaces.Stacktrace': {" 125 | ], 126 | [ 127 | 302, 128 | " 'frames': get_stack_info(frames," 129 | ], 130 | [ 131 | 303, 132 | " transformer=self.transform)" 133 | ], 134 | [ 135 | 304, 136 | " }," 137 | ], 138 | [ 139 | 305, 140 | " })" 141 | ], 142 | [ 143 | 306, 144 | "" 145 | ], 146 | [ 147 | 307, 148 | " if 'sentry.interfaces.Stacktrace' in data:" 149 | ], 150 | [ 151 | 308, 152 | " if self.include_paths:" 153 | ] 154 | ], 155 | "symbolAddr": null 156 | }, 157 | { 158 | "function": "capture", 159 | "instructionOffset": null, 160 | "errors": null, 161 | "colNo": null, 162 | "module": "raven.base", 163 | "package": null, 164 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 165 | "inApp": false, 166 | "instructionAddr": null, 167 | "filename": "raven/base.py", 168 | "platform": null, 169 | "vars": { 170 | "'event_type'": "'raven.events.Message'", 171 | "'date'": null, 172 | "'extra'": { 173 | "'go_deeper'": [ 174 | [ 175 | { 176 | "'bar'": "'\\'\\\\\\'\\\\\\\\\\\\\\'[\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"]\\\\\\\\\\\\\\'\\\\\\'\\''", 177 | "'foo'": "'\\'\\\\\\'\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\'\\\\\\'\\''" 178 | } 179 | ] 180 | ], 181 | "'user'": "'dcramer'", 182 | "'loadavg'": [ 183 | 0.37255859375, 184 | 0.5341796875, 185 | 0.62939453125 186 | ] 187 | }, 188 | "'stack'": true, 189 | "'tags'": null, 190 | "'data'": null, 191 | "'self'": "", 192 | "'time_spent'": null, 193 | "'kwargs'": { 194 | "'message'": "'This is a test message generated using ``raven test``'", 195 | "'level'": 20 196 | } 197 | }, 198 | "lineNo": 459, 199 | "context": [ 200 | [ 201 | 454, 202 | " if not self.is_enabled():" 203 | ], 204 | [ 205 | 455, 206 | " return" 207 | ], 208 | [ 209 | 456, 210 | "" 211 | ], 212 | [ 213 | 457, 214 | " data = self.build_msg(" 215 | ], 216 | [ 217 | 458, 218 | " event_type, data, date, time_spent, extra, stack, tags=tags," 219 | ], 220 | [ 221 | 459, 222 | " **kwargs)" 223 | ], 224 | [ 225 | 460, 226 | "" 227 | ], 228 | [ 229 | 461, 230 | " self.send(**data)" 231 | ], 232 | [ 233 | 462, 234 | "" 235 | ], 236 | [ 237 | 463, 238 | " return (data.get('event_id'),)" 239 | ], 240 | [ 241 | 464, 242 | "" 243 | ] 244 | ], 245 | "symbolAddr": null 246 | }, 247 | { 248 | "function": "captureMessage", 249 | "instructionOffset": null, 250 | "errors": null, 251 | "colNo": null, 252 | "module": "raven.base", 253 | "package": null, 254 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 255 | "inApp": false, 256 | "instructionAddr": null, 257 | "filename": "raven/base.py", 258 | "platform": null, 259 | "vars": { 260 | "'message'": "'This is a test message generated using ``raven test``'", 261 | "'kwargs'": { 262 | "'extra'": { 263 | "'go_deeper'": [ 264 | [ 265 | "'\\'\\\\\\'\\\\\\\\\\\\\\'{\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\": [\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"], \"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'foo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\": \"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"}\\\\\\\\\\\\\\'\\\\\\'\\''" 266 | ] 267 | ], 268 | "'user'": "'dcramer'", 269 | "'loadavg'": [ 270 | 0.37255859375, 271 | 0.5341796875, 272 | 0.62939453125 273 | ] 274 | }, 275 | "'tags'": null, 276 | "'data'": null, 277 | "'level'": 20, 278 | "'stack'": true 279 | }, 280 | "'self'": "" 281 | }, 282 | "lineNo": 577, 283 | "context": [ 284 | [ 285 | 572, 286 | " \"\"\"" 287 | ], 288 | [ 289 | 573, 290 | " Creates an event from ``message``." 291 | ], 292 | [ 293 | 574, 294 | "" 295 | ], 296 | [ 297 | 575, 298 | " >>> client.captureMessage('My event just happened!')" 299 | ], 300 | [ 301 | 576, 302 | " \"\"\"" 303 | ], 304 | [ 305 | 577, 306 | " return self.capture('raven.events.Message', message=message, **kwargs)" 307 | ], 308 | [ 309 | 578, 310 | "" 311 | ], 312 | [ 313 | 579, 314 | " def captureException(self, exc_info=None, **kwargs):" 315 | ], 316 | [ 317 | 580, 318 | " \"\"\"" 319 | ], 320 | [ 321 | 581, 322 | " Creates an event from an exception." 323 | ], 324 | [ 325 | 582, 326 | "" 327 | ] 328 | ], 329 | "symbolAddr": null 330 | }, 331 | { 332 | "function": "send_test_message", 333 | "instructionOffset": null, 334 | "errors": null, 335 | "colNo": null, 336 | "module": "raven.scripts.runner", 337 | "package": null, 338 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/scripts/runner.py", 339 | "inApp": false, 340 | "instructionAddr": null, 341 | "filename": "raven/scripts/runner.py", 342 | "platform": null, 343 | "vars": { 344 | "'client'": "", 345 | "'options'": { 346 | "'tags'": null, 347 | "'data'": null 348 | }, 349 | "'data'": null, 350 | "'k'": "'secret_key'" 351 | }, 352 | "lineNo": 77, 353 | "context": [ 354 | [ 355 | 72, 356 | " level=logging.INFO," 357 | ], 358 | [ 359 | 73, 360 | " stack=True," 361 | ], 362 | [ 363 | 74, 364 | " tags=options.get('tags', {})," 365 | ], 366 | [ 367 | 75, 368 | " extra={" 369 | ], 370 | [ 371 | 76, 372 | " 'user': get_uid()," 373 | ], 374 | [ 375 | 77, 376 | " 'loadavg': get_loadavg()," 377 | ], 378 | [ 379 | 78, 380 | " }," 381 | ], 382 | [ 383 | 79, 384 | " ))" 385 | ], 386 | [ 387 | 80, 388 | "" 389 | ], 390 | [ 391 | 81, 392 | " if client.state.did_fail():" 393 | ], 394 | [ 395 | 82, 396 | " print('error!')" 397 | ] 398 | ], 399 | "symbolAddr": null 400 | }, 401 | { 402 | "function": "main", 403 | "instructionOffset": null, 404 | "errors": null, 405 | "colNo": null, 406 | "module": "raven.scripts.runner", 407 | "package": null, 408 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/scripts/runner.py", 409 | "inApp": false, 410 | "instructionAddr": null, 411 | "filename": "raven/scripts/runner.py", 412 | "platform": null, 413 | "vars": { 414 | "'root'": "", 415 | "'parser'": "", 416 | "'dsn'": "'https://ebc35f33e151401f9deac549978bda11:f3403f81e12e4c24942d505f086b2cad@app.getsentry.com/1'", 417 | "'opts'": "", 418 | "'client'": "", 419 | "'args'": [ 420 | "'test'", 421 | "'https://ebc35f33e151401f9deac549978bda11:f3403f81e12e4c24942d505f086b2cad@app.getsentry.com/1'" 422 | ] 423 | }, 424 | "lineNo": 112, 425 | "context": [ 426 | [ 427 | 107, 428 | " print(\"Using DSN configuration:\")" 429 | ], 430 | [ 431 | 108, 432 | " print(\" \", dsn)" 433 | ], 434 | [ 435 | 109, 436 | " print()" 437 | ], 438 | [ 439 | 110, 440 | "" 441 | ], 442 | [ 443 | 111, 444 | " client = Client(dsn, include_paths=['raven'])" 445 | ], 446 | [ 447 | 112, 448 | " send_test_message(client, opts.__dict__)" 449 | ] 450 | ], 451 | "symbolAddr": null 452 | } 453 | ], 454 | "framesOmitted": null, 455 | "hasSystemFrames": false 456 | } 457 | }, 458 | { 459 | "type": "template", 460 | "data": { 461 | "lineNo": 14, 462 | "context": [ 463 | [ 464 | 11, 465 | "{% endif %}\n" 466 | ], 467 | [ 468 | 12, 469 | "\n" 470 | ], 471 | [ 472 | 13, 473 | "\n" 482 | ], 483 | [ 484 | 16, 485 | "\t\n" 486 | ], 487 | [ 488 | 17, 489 | "\t\t\n" 490 | ] 491 | ], 492 | "filename": "debug_toolbar/base.html" 493 | } 494 | }, 495 | { 496 | "type": "request", 497 | "data": { 498 | "cookies": [ 499 | [ 500 | "foo", 501 | "bar" 502 | ], 503 | [ 504 | "biz", 505 | "baz" 506 | ] 507 | ], 508 | "fragment": "", 509 | "headers": [ 510 | [ 511 | "Content-Type", 512 | "application/json" 513 | ], 514 | [ 515 | "Referer", 516 | "http://example.com" 517 | ], 518 | [ 519 | "User-Agent", 520 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.72 Safari/537.36" 521 | ] 522 | ], 523 | "url": "http://example.com/foo", 524 | "env": { 525 | "ENV": "prod" 526 | }, 527 | "query": "foo=bar", 528 | "data": "{\"hello\": \"world\"}", 529 | "method": "GET" 530 | } 531 | } 532 | ], 533 | "id": "1", 534 | "previousEventID": null, 535 | "message": "This is an example python exception", 536 | "packages": { 537 | "my.package": "1.0.0" 538 | }, 539 | "type": "default", 540 | "groupID": 1, 541 | "tags": [ 542 | { 543 | "value": "Chrome 28.0", 544 | "key": "browser" 545 | }, 546 | { 547 | "value": "Other", 548 | "key": "device" 549 | }, 550 | { 551 | "value": "production", 552 | "key": "environment" 553 | }, 554 | { 555 | "value": "error", 556 | "key": "level" 557 | }, 558 | { 559 | "value": "Windows 8", 560 | "key": "os" 561 | }, 562 | { 563 | "value": "15eb7269c935873b24984a5c8047547b23468ed0", 564 | "key": "release" 565 | }, 566 | { 567 | "value": "http://example.com/foo", 568 | "key": "url" 569 | }, 570 | { 571 | "value": "id:1671", 572 | "key": "user" 573 | } 574 | ], 575 | "metadata": { 576 | "title": "This is an example python exception" 577 | } 578 | } -------------------------------------------------------------------------------- /spec/fixtures/project_events.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "eventID": "ef4e4e732d2544279851cf7c1b42716e", 4 | "sdk": null, 5 | "errors": [], 6 | "platform": "python", 7 | "contexts": {}, 8 | "nextEventID": null, 9 | "size": 7911, 10 | "dateCreated": "2016-08-26T20:00:53Z", 11 | "dateReceived": "2016-08-26T20:00:53Z", 12 | "user": { 13 | "username": "getsentry", 14 | "email": "foo@example.com", 15 | "id": "1671" 16 | }, 17 | "context": { 18 | "emptyList": [], 19 | "unauthorized": false, 20 | "emptyMap": {}, 21 | "url": "http://example.org/foo/bar/", 22 | "results": [ 23 | 1, 24 | 2, 25 | 3, 26 | 4, 27 | 5 28 | ], 29 | "length": 10837790, 30 | "session": { 31 | "foo": "bar" 32 | } 33 | }, 34 | "entries": [ 35 | { 36 | "type": "message", 37 | "data": { 38 | "message": "This is an example python exception" 39 | } 40 | }, 41 | { 42 | "type": "stacktrace", 43 | "data": { 44 | "frames": [ 45 | { 46 | "function": "build_msg", 47 | "instructionOffset": null, 48 | "errors": null, 49 | "colNo": null, 50 | "module": "raven.base", 51 | "package": null, 52 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 53 | "inApp": false, 54 | "instructionAddr": null, 55 | "filename": "raven/base.py", 56 | "platform": null, 57 | "vars": { 58 | "'frames'": "", 59 | "'culprit'": null, 60 | "'event_type'": "'raven.events.Message'", 61 | "'date'": "datetime.datetime(2013, 8, 13, 3, 8, 24, 880386)", 62 | "'extra'": { 63 | "'go_deeper'": [ 64 | [ 65 | { 66 | "'bar'": "'\\'\\\\\\'\\\\\\\\\\\\\\'[\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"]\\\\\\\\\\\\\\'\\\\\\'\\''", 67 | "'foo'": "'\\'\\\\\\'\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\'\\\\\\'\\''" 68 | } 69 | ] 70 | ], 71 | "'user'": "'dcramer'", 72 | "'loadavg'": [ 73 | 0.37255859375, 74 | 0.5341796875, 75 | 0.62939453125 76 | ] 77 | }, 78 | "'v'": { 79 | "'message'": "u'This is a test message generated using ``raven test``'", 80 | "'params'": [] 81 | }, 82 | "'kwargs'": { 83 | "'message'": "'This is a test message generated using ``raven test``'", 84 | "'level'": 20 85 | }, 86 | "'event_id'": "'54a322436e1b47b88e239b78998ae742'", 87 | "'tags'": null, 88 | "'data'": { 89 | "'sentry.interfaces.Message'": { 90 | "'message'": "u'This is a test message generated using ``raven test``'", 91 | "'params'": [] 92 | }, 93 | "'message'": "u'This is a test message generated using ``raven test``'" 94 | }, 95 | "'self'": "", 96 | "'time_spent'": null, 97 | "'result'": { 98 | "'sentry.interfaces.Message'": { 99 | "'message'": "u'This is a test message generated using ``raven test``'", 100 | "'params'": [] 101 | }, 102 | "'message'": "u'This is a test message generated using ``raven test``'" 103 | }, 104 | "'stack'": true, 105 | "'handler'": "", 106 | "'k'": "'sentry.interfaces.Message'", 107 | "'public_key'": null 108 | }, 109 | "lineNo": 303, 110 | "context": [ 111 | [ 112 | 298, 113 | " frames = stack" 114 | ], 115 | [ 116 | 299, 117 | "" 118 | ], 119 | [ 120 | 300, 121 | " data.update({" 122 | ], 123 | [ 124 | 301, 125 | " 'sentry.interfaces.Stacktrace': {" 126 | ], 127 | [ 128 | 302, 129 | " 'frames': get_stack_info(frames," 130 | ], 131 | [ 132 | 303, 133 | " transformer=self.transform)" 134 | ], 135 | [ 136 | 304, 137 | " }," 138 | ], 139 | [ 140 | 305, 141 | " })" 142 | ], 143 | [ 144 | 306, 145 | "" 146 | ], 147 | [ 148 | 307, 149 | " if 'sentry.interfaces.Stacktrace' in data:" 150 | ], 151 | [ 152 | 308, 153 | " if self.include_paths:" 154 | ] 155 | ], 156 | "symbolAddr": null 157 | }, 158 | { 159 | "function": "capture", 160 | "instructionOffset": null, 161 | "errors": null, 162 | "colNo": null, 163 | "module": "raven.base", 164 | "package": null, 165 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 166 | "inApp": false, 167 | "instructionAddr": null, 168 | "filename": "raven/base.py", 169 | "platform": null, 170 | "vars": { 171 | "'event_type'": "'raven.events.Message'", 172 | "'date'": null, 173 | "'extra'": { 174 | "'go_deeper'": [ 175 | [ 176 | { 177 | "'bar'": "'\\'\\\\\\'\\\\\\\\\\\\\\'[\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"]\\\\\\\\\\\\\\'\\\\\\'\\''", 178 | "'foo'": "'\\'\\\\\\'\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"\\\\\\\\\\\\\\'\\\\\\'\\''" 179 | } 180 | ] 181 | ], 182 | "'user'": "'dcramer'", 183 | "'loadavg'": [ 184 | 0.37255859375, 185 | 0.5341796875, 186 | 0.62939453125 187 | ] 188 | }, 189 | "'stack'": true, 190 | "'tags'": null, 191 | "'data'": null, 192 | "'self'": "", 193 | "'time_spent'": null, 194 | "'kwargs'": { 195 | "'message'": "'This is a test message generated using ``raven test``'", 196 | "'level'": 20 197 | } 198 | }, 199 | "lineNo": 459, 200 | "context": [ 201 | [ 202 | 454, 203 | " if not self.is_enabled():" 204 | ], 205 | [ 206 | 455, 207 | " return" 208 | ], 209 | [ 210 | 456, 211 | "" 212 | ], 213 | [ 214 | 457, 215 | " data = self.build_msg(" 216 | ], 217 | [ 218 | 458, 219 | " event_type, data, date, time_spent, extra, stack, tags=tags," 220 | ], 221 | [ 222 | 459, 223 | " **kwargs)" 224 | ], 225 | [ 226 | 460, 227 | "" 228 | ], 229 | [ 230 | 461, 231 | " self.send(**data)" 232 | ], 233 | [ 234 | 462, 235 | "" 236 | ], 237 | [ 238 | 463, 239 | " return (data.get('event_id'),)" 240 | ], 241 | [ 242 | 464, 243 | "" 244 | ] 245 | ], 246 | "symbolAddr": null 247 | }, 248 | { 249 | "function": "captureMessage", 250 | "instructionOffset": null, 251 | "errors": null, 252 | "colNo": null, 253 | "module": "raven.base", 254 | "package": null, 255 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/base.py", 256 | "inApp": false, 257 | "instructionAddr": null, 258 | "filename": "raven/base.py", 259 | "platform": null, 260 | "vars": { 261 | "'message'": "'This is a test message generated using ``raven test``'", 262 | "'kwargs'": { 263 | "'extra'": { 264 | "'go_deeper'": [ 265 | [ 266 | "'\\'\\\\\\'\\\\\\\\\\\\\\'{\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\": [\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"], \"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'foo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\": \"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'bar\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\"}\\\\\\\\\\\\\\'\\\\\\'\\''" 267 | ] 268 | ], 269 | "'user'": "'dcramer'", 270 | "'loadavg'": [ 271 | 0.37255859375, 272 | 0.5341796875, 273 | 0.62939453125 274 | ] 275 | }, 276 | "'tags'": null, 277 | "'data'": null, 278 | "'level'": 20, 279 | "'stack'": true 280 | }, 281 | "'self'": "" 282 | }, 283 | "lineNo": 577, 284 | "context": [ 285 | [ 286 | 572, 287 | " \"\"\"" 288 | ], 289 | [ 290 | 573, 291 | " Creates an event from ``message``." 292 | ], 293 | [ 294 | 574, 295 | "" 296 | ], 297 | [ 298 | 575, 299 | " >>> client.captureMessage('My event just happened!')" 300 | ], 301 | [ 302 | 576, 303 | " \"\"\"" 304 | ], 305 | [ 306 | 577, 307 | " return self.capture('raven.events.Message', message=message, **kwargs)" 308 | ], 309 | [ 310 | 578, 311 | "" 312 | ], 313 | [ 314 | 579, 315 | " def captureException(self, exc_info=None, **kwargs):" 316 | ], 317 | [ 318 | 580, 319 | " \"\"\"" 320 | ], 321 | [ 322 | 581, 323 | " Creates an event from an exception." 324 | ], 325 | [ 326 | 582, 327 | "" 328 | ] 329 | ], 330 | "symbolAddr": null 331 | }, 332 | { 333 | "function": "send_test_message", 334 | "instructionOffset": null, 335 | "errors": null, 336 | "colNo": null, 337 | "module": "raven.scripts.runner", 338 | "package": null, 339 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/scripts/runner.py", 340 | "inApp": false, 341 | "instructionAddr": null, 342 | "filename": "raven/scripts/runner.py", 343 | "platform": null, 344 | "vars": { 345 | "'client'": "", 346 | "'options'": { 347 | "'tags'": null, 348 | "'data'": null 349 | }, 350 | "'data'": null, 351 | "'k'": "'secret_key'" 352 | }, 353 | "lineNo": 77, 354 | "context": [ 355 | [ 356 | 72, 357 | " level=logging.INFO," 358 | ], 359 | [ 360 | 73, 361 | " stack=True," 362 | ], 363 | [ 364 | 74, 365 | " tags=options.get('tags', {})," 366 | ], 367 | [ 368 | 75, 369 | " extra={" 370 | ], 371 | [ 372 | 76, 373 | " 'user': get_uid()," 374 | ], 375 | [ 376 | 77, 377 | " 'loadavg': get_loadavg()," 378 | ], 379 | [ 380 | 78, 381 | " }," 382 | ], 383 | [ 384 | 79, 385 | " ))" 386 | ], 387 | [ 388 | 80, 389 | "" 390 | ], 391 | [ 392 | 81, 393 | " if client.state.did_fail():" 394 | ], 395 | [ 396 | 82, 397 | " print('error!')" 398 | ] 399 | ], 400 | "symbolAddr": null 401 | }, 402 | { 403 | "function": "main", 404 | "instructionOffset": null, 405 | "errors": null, 406 | "colNo": null, 407 | "module": "raven.scripts.runner", 408 | "package": null, 409 | "absPath": "/home/ubuntu/.virtualenvs/getsentry/src/raven/raven/scripts/runner.py", 410 | "inApp": false, 411 | "instructionAddr": null, 412 | "filename": "raven/scripts/runner.py", 413 | "platform": null, 414 | "vars": { 415 | "'root'": "", 416 | "'parser'": "", 417 | "'dsn'": "'https://ebc35f33e151401f9deac549978bda11:f3403f81e12e4c24942d505f086b2cad@app.getsentry.com/1'", 418 | "'opts'": "", 419 | "'client'": "", 420 | "'args'": [ 421 | "'test'", 422 | "'https://ebc35f33e151401f9deac549978bda11:f3403f81e12e4c24942d505f086b2cad@app.getsentry.com/1'" 423 | ] 424 | }, 425 | "lineNo": 112, 426 | "context": [ 427 | [ 428 | 107, 429 | " print(\"Using DSN configuration:\")" 430 | ], 431 | [ 432 | 108, 433 | " print(\" \", dsn)" 434 | ], 435 | [ 436 | 109, 437 | " print()" 438 | ], 439 | [ 440 | 110, 441 | "" 442 | ], 443 | [ 444 | 111, 445 | " client = Client(dsn, include_paths=['raven'])" 446 | ], 447 | [ 448 | 112, 449 | " send_test_message(client, opts.__dict__)" 450 | ] 451 | ], 452 | "symbolAddr": null 453 | } 454 | ], 455 | "framesOmitted": null, 456 | "hasSystemFrames": false 457 | } 458 | }, 459 | { 460 | "type": "template", 461 | "data": { 462 | "lineNo": 14, 463 | "context": [ 464 | [ 465 | 11, 466 | "{% endif %}\n" 467 | ], 468 | [ 469 | 12, 470 | "\n" 471 | ], 472 | [ 473 | 13, 474 | "\n" 483 | ], 484 | [ 485 | 16, 486 | "\t\n" 487 | ], 488 | [ 489 | 17, 490 | "\t\t\n" 491 | ] 492 | ], 493 | "filename": "debug_toolbar/base.html" 494 | } 495 | }, 496 | { 497 | "type": "request", 498 | "data": { 499 | "cookies": [ 500 | [ 501 | "foo", 502 | "bar" 503 | ], 504 | [ 505 | "biz", 506 | "baz" 507 | ] 508 | ], 509 | "fragment": "", 510 | "headers": [ 511 | [ 512 | "Content-Type", 513 | "application/json" 514 | ], 515 | [ 516 | "Referer", 517 | "http://example.com" 518 | ], 519 | [ 520 | "User-Agent", 521 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.72 Safari/537.36" 522 | ] 523 | ], 524 | "url": "http://example.com/foo", 525 | "env": { 526 | "ENV": "prod" 527 | }, 528 | "query": "foo=bar", 529 | "data": "{\"hello\": \"world\"}", 530 | "method": "GET" 531 | } 532 | } 533 | ], 534 | "id": "1", 535 | "previousEventID": null, 536 | "message": "This is an example python exception", 537 | "packages": { 538 | "my.package": "1.0.0" 539 | }, 540 | "type": "default", 541 | "groupID": 1, 542 | "tags": [ 543 | { 544 | "value": "Chrome 28.0", 545 | "key": "browser" 546 | }, 547 | { 548 | "value": "Other", 549 | "key": "device" 550 | }, 551 | { 552 | "value": "production", 553 | "key": "environment" 554 | }, 555 | { 556 | "value": "error", 557 | "key": "level" 558 | }, 559 | { 560 | "value": "Windows 8", 561 | "key": "os" 562 | }, 563 | { 564 | "value": "15eb7269c935873b24984a5c8047547b23468ed0", 565 | "key": "release" 566 | }, 567 | { 568 | "value": "http://example.com/foo", 569 | "key": "url" 570 | }, 571 | { 572 | "value": "id:1671", 573 | "key": "user" 574 | } 575 | ], 576 | "metadata": { 577 | "title": "This is an example python exception" 578 | } 579 | } 580 | ] -------------------------------------------------------------------------------- /spec/fixtures/project_issues.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "lastSeen": "2016-08-26T20:00:55Z", 4 | "id": "2", 5 | "userCount": 0, 6 | "stats": { 7 | "24h": [ 8 | [ 9 | 1472158800, 10 | 729 11 | ], 12 | [ 13 | 1472162400, 14 | 753 15 | ], 16 | [ 17 | 1472166000, 18 | 290 19 | ], 20 | [ 21 | 1472169600, 22 | 922 23 | ], 24 | [ 25 | 1472173200, 26 | 933 27 | ], 28 | [ 29 | 1472176800, 30 | 502 31 | ], 32 | [ 33 | 1472180400, 34 | 383 35 | ], 36 | [ 37 | 1472184000, 38 | 986 39 | ], 40 | [ 41 | 1472187600, 42 | 374 43 | ], 44 | [ 45 | 1472191200, 46 | 175 47 | ], 48 | [ 49 | 1472194800, 50 | 634 51 | ], 52 | [ 53 | 1472198400, 54 | 157 55 | ], 56 | [ 57 | 1472202000, 58 | 961 59 | ], 60 | [ 61 | 1472205600, 62 | 643 63 | ], 64 | [ 65 | 1472209200, 66 | 761 67 | ], 68 | [ 69 | 1472212800, 70 | 972 71 | ], 72 | [ 73 | 1472216400, 74 | 312 75 | ], 76 | [ 77 | 1472220000, 78 | 326 79 | ], 80 | [ 81 | 1472223600, 82 | 185 83 | ], 84 | [ 85 | 1472227200, 86 | 379 87 | ], 88 | [ 89 | 1472230800, 90 | 290 91 | ], 92 | [ 93 | 1472234400, 94 | 201 95 | ], 96 | [ 97 | 1472238000, 98 | 432 99 | ], 100 | [ 101 | 1472241600, 102 | 288 103 | ] 104 | ] 105 | }, 106 | "culprit": "org.hsqldb.jdbc.Util in throwError", 107 | "title": "This is an example java exception javax.servlet.ServletException Something bad happened", 108 | "numComments": 0, 109 | "assignedTo": null, 110 | "logger": null, 111 | "type": "error", 112 | "annotations": [], 113 | "metadata": { 114 | "type": "javax.servlet.ServletException", 115 | "value": "Something bad happened" 116 | }, 117 | "status": "unresolved", 118 | "isPublic": false, 119 | "permalink": "https://app.getsentry.com/the-interstellar-jurisdiction/pump-station/issues/2/", 120 | "shortId": "PUMP-STATION-2", 121 | "shareId": "322e32", 122 | "firstSeen": "2016-08-26T20:00:55Z", 123 | "count": "1", 124 | "hasSeen": false, 125 | "level": "error", 126 | "isSubscribed": false, 127 | "isBookmarked": false, 128 | "project": { 129 | "name": "Pump Station", 130 | "slug": "pump-station" 131 | }, 132 | "statusDetails": {} 133 | }, 134 | { 135 | "lastSeen": "2016-08-26T20:00:53Z", 136 | "id": "1", 137 | "userCount": 0, 138 | "stats": { 139 | "24h": [ 140 | [ 141 | 1472158800, 142 | 192 143 | ], 144 | [ 145 | 1472162400, 146 | 298 147 | ], 148 | [ 149 | 1472166000, 150 | 268 151 | ], 152 | [ 153 | 1472169600, 154 | 869 155 | ], 156 | [ 157 | 1472173200, 158 | 785 159 | ], 160 | [ 161 | 1472176800, 162 | 298 163 | ], 164 | [ 165 | 1472180400, 166 | 958 167 | ], 168 | [ 169 | 1472184000, 170 | 602 171 | ], 172 | [ 173 | 1472187600, 174 | 366 175 | ], 176 | [ 177 | 1472191200, 178 | 242 179 | ], 180 | [ 181 | 1472194800, 182 | 874 183 | ], 184 | [ 185 | 1472198400, 186 | 747 187 | ], 188 | [ 189 | 1472202000, 190 | 986 191 | ], 192 | [ 193 | 1472205600, 194 | 523 195 | ], 196 | [ 197 | 1472209200, 198 | 748 199 | ], 200 | [ 201 | 1472212800, 202 | 747 203 | ], 204 | [ 205 | 1472216400, 206 | 908 207 | ], 208 | [ 209 | 1472220000, 210 | 868 211 | ], 212 | [ 213 | 1472223600, 214 | 719 215 | ], 216 | [ 217 | 1472227200, 218 | 500 219 | ], 220 | [ 221 | 1472230800, 222 | 741 223 | ], 224 | [ 225 | 1472234400, 226 | 613 227 | ], 228 | [ 229 | 1472238000, 230 | 481 231 | ], 232 | [ 233 | 1472241600, 234 | 307 235 | ] 236 | ] 237 | }, 238 | "culprit": "raven.scripts.runner in main", 239 | "title": "This is an example python exception", 240 | "numComments": 0, 241 | "assignedTo": null, 242 | "logger": null, 243 | "type": "default", 244 | "annotations": [], 245 | "metadata": { 246 | "title": "This is an example python exception" 247 | }, 248 | "status": "unresolved", 249 | "isPublic": false, 250 | "permalink": "https://app.getsentry.com/the-interstellar-jurisdiction/pump-station/issues/1/", 251 | "shortId": "PUMP-STATION-1", 252 | "shareId": "322e31", 253 | "firstSeen": "2016-08-26T20:00:53Z", 254 | "count": "1", 255 | "hasSeen": false, 256 | "level": "error", 257 | "isSubscribed": false, 258 | "isBookmarked": false, 259 | "project": { 260 | "name": "Pump Station", 261 | "slug": "pump-station" 262 | }, 263 | "statusDetails": {} 264 | } 265 | ] -------------------------------------------------------------------------------- /spec/fixtures/project_stats.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | 1472158800, 4 | 1012 5 | ], 6 | [ 7 | 1472162400, 8 | 1155 9 | ], 10 | [ 11 | 1472166000, 12 | 613 13 | ], 14 | [ 15 | 1472169600, 16 | 1969 17 | ], 18 | [ 19 | 1472173200, 20 | 1889 21 | ], 22 | [ 23 | 1472176800, 24 | 879 25 | ], 26 | [ 27 | 1472180400, 28 | 1474 29 | ], 30 | [ 31 | 1472184000, 32 | 1746 33 | ], 34 | [ 35 | 1472187600, 36 | 813 37 | ], 38 | [ 39 | 1472191200, 40 | 458 41 | ], 42 | [ 43 | 1472194800, 44 | 1658 45 | ], 46 | [ 47 | 1472198400, 48 | 993 49 | ], 50 | [ 51 | 1472202000, 52 | 2141 53 | ], 54 | [ 55 | 1472205600, 56 | 1282 57 | ], 58 | [ 59 | 1472209200, 60 | 1659 61 | ], 62 | [ 63 | 1472212800, 64 | 1890 65 | ], 66 | [ 67 | 1472216400, 68 | 1341 69 | ], 70 | [ 71 | 1472220000, 72 | 1312 73 | ], 74 | [ 75 | 1472223600, 76 | 993 77 | ], 78 | [ 79 | 1472227200, 80 | 966 81 | ], 82 | [ 83 | 1472230800, 84 | 1134 85 | ], 86 | [ 87 | 1472234400, 88 | 895 89 | ], 90 | [ 91 | 1472238000, 92 | 1000 93 | ], 94 | [ 95 | 1472241600, 96 | 608 97 | ] 98 | ] -------------------------------------------------------------------------------- /spec/fixtures/projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "status": "active", 4 | "slug": "the-spoiled-yoghurt", 5 | "defaultEnvironment": null, 6 | "features": [], 7 | "color": "#bf6e3f", 8 | "isPublic": false, 9 | "dateCreated": "2016-08-26T20:01:03.113Z", 10 | "platforms": [], 11 | "callSign": "THE-SPOILED-YOGHURT", 12 | "firstEvent": null, 13 | "organization": { 14 | "name": "The Interstellar Jurisdiction", 15 | "slug": "the-interstellar-jurisdiction", 16 | "id": "2", 17 | "isEarlyAdopter": false, 18 | "dateCreated": "2016-08-26T20:00:53.596Z" 19 | }, 20 | "isBookmarked": false, 21 | "callSignReviewed": false, 22 | "id": "4", 23 | "name": "The Spoiled Yoghurt" 24 | }, 25 | { 26 | "status": "active", 27 | "slug": "prime-mover", 28 | "defaultEnvironment": null, 29 | "features": [], 30 | "color": "#bf5b3f", 31 | "isPublic": false, 32 | "dateCreated": "2016-08-26T20:00:56.523Z", 33 | "platforms": [], 34 | "callSign": "PRIME-MOVER", 35 | "firstEvent": null, 36 | "organization": { 37 | "name": "The Interstellar Jurisdiction", 38 | "slug": "the-interstellar-jurisdiction", 39 | "id": "2", 40 | "isEarlyAdopter": false, 41 | "dateCreated": "2016-08-26T20:00:53.596Z" 42 | }, 43 | "isBookmarked": false, 44 | "callSignReviewed": false, 45 | "id": "3", 46 | "name": "Prime Mover" 47 | }, 48 | { 49 | "status": "active", 50 | "slug": "pump-station", 51 | "defaultEnvironment": null, 52 | "features": [], 53 | "color": "#3fbf7f", 54 | "isPublic": false, 55 | "dateCreated": "2016-08-26T20:00:53.610Z", 56 | "platforms": [], 57 | "callSign": "PUMP-STATION", 58 | "firstEvent": null, 59 | "organization": { 60 | "name": "The Interstellar Jurisdiction", 61 | "slug": "the-interstellar-jurisdiction", 62 | "id": "2", 63 | "isEarlyAdopter": false, 64 | "dateCreated": "2016-08-26T20:00:53.596Z" 65 | }, 66 | "isBookmarked": false, 67 | "callSignReviewed": false, 68 | "id": "2", 69 | "name": "Pump Station" 70 | } 71 | ] -------------------------------------------------------------------------------- /spec/fixtures/release.json: -------------------------------------------------------------------------------- 1 | { 2 | "dateReleased": null, 3 | "url": null, 4 | "ref": null, 5 | "owner": null, 6 | "dateCreated": "2016-09-03T13:13:06.091Z", 7 | "lastEvent": "2016-09-03T13:13:06.181Z", 8 | "version": "6945297bf064ca1ce68cfba53fbb91e2ef47eec6", 9 | "firstEvent": "2016-09-03T13:13:06.181Z", 10 | "shortVersion": "6945297bf064", 11 | "dateStarted": null, 12 | "newGroups": 0, 13 | "data": {} 14 | } -------------------------------------------------------------------------------- /spec/fixtures/remove_issue.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /spec/fixtures/team.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "powerful-abolitionist", 3 | "name": "Powerful Abolitionist", 4 | "hasAccess": true, 5 | "isPending": false, 6 | "dateCreated": "2016-08-30T21:22:52.612Z", 7 | "isMember": false, 8 | "organization": { 9 | "name": "The Interstellar Jurisdiction", 10 | "slug": "the-interstellar-jurisdiction", 11 | "id": "2", 12 | "isEarlyAdopter": false, 13 | "dateCreated": "2016-08-30T21:22:52.601Z" 14 | }, 15 | "id": "2" 16 | } -------------------------------------------------------------------------------- /spec/fixtures/update_client_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Quite Positive Key", 3 | "dsn": { 4 | "secret": "https://b692781e8be347b98722d62506fc8aa8:e65559b2ca5b4f98b2aeda552432eb9c@app.getsentry.com/2", 5 | "csp": "https://app.getsentry.com/api/2/csp-report/?sentry_key=b692781e8be347b98722d62506fc8aa8", 6 | "public": "https://b692781e8be347b98722d62506fc8aa8@app.getsentry.com/2" 7 | }, 8 | "secret": "e65559b2ca5b4f98b2aeda552432eb9c", 9 | "id": "b692781e8be347b98722d62506fc8aa8", 10 | "dateCreated": "2016-08-26T20:01:13.396Z", 11 | "public": "b692781e8be347b98722d62506fc8aa8" 12 | } -------------------------------------------------------------------------------- /spec/fixtures/update_issue.json: -------------------------------------------------------------------------------- 1 | { 2 | "lastSeen": "2016-04-03T15:09:43Z", 3 | "numComments": 0, 4 | "userCount": 0, 5 | "culprit": "lib/tasks/device_task.rake in rescue in do_send_data at line 140", 6 | "title": "Errno::EIO: Input/output error @ io_write - ", 7 | "id": "120732258", 8 | "assignedTo": { 9 | "username": "thierry.xing@gmail.com", 10 | "isManaged": false, 11 | "id": "63061", 12 | "isActive": true, 13 | "has2fa": false, 14 | "name": "Thierry Xing", 15 | "avatarUrl": "https://secure.gravatar.com/avatar/acdc4bff4797737fffafc7b7261f2e07?s=32&d=mm", 16 | "dateJoined": "2016-03-20T10:59:50.048Z", 17 | "avatar": { 18 | "avatarUuid": null, 19 | "avatarType": "gravatar" 20 | }, 21 | "lastLogin": "2016-08-28T09:38:34.846Z", 22 | "email": "thierry.xing@gmail.com" 23 | }, 24 | "logger": "rake", 25 | "type": "error", 26 | "annotations": [], 27 | "metadata": { 28 | "type": "Net::OpenTimeout", 29 | "value": "execution expired" 30 | }, 31 | "status": "resolved", 32 | "isPublic": false, 33 | "hasSeen": true, 34 | "shortId": "EPEIUS-2T", 35 | "shareId": "37313138352e313230373332323538", 36 | "firstSeen": "2016-04-03T15:09:43Z", 37 | "count": "1", 38 | "permalink": "https://app.getsentry.com/sentry-sc/epeius/issues/120732258/", 39 | "level": "error", 40 | "isSubscribed": true, 41 | "isBookmarked": true, 42 | "project": { 43 | "name": "Epeius", 44 | "slug": "epeius" 45 | }, 46 | "statusDetails": {} 47 | } -------------------------------------------------------------------------------- /spec/fixtures/update_organization.json: -------------------------------------------------------------------------------- 1 | { 2 | "pendingAccessRequests": 0, 3 | "slug": "impeccably-designated", 4 | "name": "Impeccably Designated", 5 | "quota": { 6 | "projectLimit": 100, 7 | "maxRate": 0 8 | }, 9 | "dateCreated": "2016-08-26T20:01:13.416Z", 10 | "access": [], 11 | "teams": [], 12 | "onboardingTasks": [], 13 | "id": "3", 14 | "isEarlyAdopter": false, 15 | "features": [ 16 | "sso", 17 | "api-keys", 18 | "open-membership", 19 | "shared-issues" 20 | ] 21 | } -------------------------------------------------------------------------------- /spec/fixtures/update_project.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "active", 3 | "options": { 4 | "sentry:origins": "http://example.com\nhttp://example.invalid", 5 | "sentry:resolve_age": 0 6 | }, 7 | "defaultEnvironment": null, 8 | "features": [], 9 | "color": "#bf803f", 10 | "slug": "plane-proxy", 11 | "isPublic": false, 12 | "dateCreated": "2016-08-26T20:01:13.463Z", 13 | "platforms": [], 14 | "callSign": "PLANE-PROXY", 15 | "firstEvent": null, 16 | "isBookmarked": false, 17 | "callSignReviewed": false, 18 | "id": "5", 19 | "name": "Plane Proxy" 20 | } -------------------------------------------------------------------------------- /spec/sentry-api/client/events_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryApi::Client do 4 | 5 | describe ".issue" do 6 | before do 7 | stub_get("/issues/1/", "issue") 8 | @issue = SentryApi.issue("1") 9 | end 10 | 11 | it "should get the correct resource" do 12 | expect(a_get("/issues/1/")).to have_been_made 13 | end 14 | 15 | it "should return a response of issue" do 16 | expect(@issue.title).to eq("This is an example python exception") 17 | end 18 | end 19 | 20 | describe ".issue_events" do 21 | before do 22 | stub_get("/issues/1/events/", "issue_events") 23 | @events = SentryApi.issue_events("1") 24 | end 25 | 26 | it "should get the correct resource" do 27 | expect(a_get("/issues/1/events/")).to have_been_made 28 | end 29 | 30 | it "should return an array response of events" do 31 | expect(@events.first.eventID).to eq("03fc4d526a3d47c7b2219ca1f3c81cf7") 32 | end 33 | end 34 | 35 | describe ".issue_hashes" do 36 | before do 37 | stub_get("/issues/1/hashes/", "issue_hashes") 38 | @hashes = SentryApi.issue_hashes("1") 39 | end 40 | 41 | it "should get the correct resource" do 42 | expect(a_get("/issues/1/hashes/")).to have_been_made 43 | end 44 | 45 | it "should return an array response of hashes" do 46 | expect(@hashes.first.id).to eq("c4a4d06bc314205bb3b6bdb612dde7f1") 47 | end 48 | end 49 | 50 | describe ".remove_issue" do 51 | before do 52 | stub_delete("/issues/1/", "remove_issue") 53 | @hashes = SentryApi.remove_issue("1") 54 | end 55 | 56 | it "should get the correct resource" do 57 | expect(a_delete("/issues/1/")).to have_been_made 58 | end 59 | end 60 | 61 | describe ".remove_issue" do 62 | before do 63 | stub_delete("/issues/1/", "remove_issue") 64 | @hashes = SentryApi.remove_issue("1") 65 | end 66 | 67 | it "should get the correct resource" do 68 | expect(a_delete("/issues/1/")).to have_been_made 69 | end 70 | end 71 | 72 | describe ".update_issue" do 73 | before do 74 | stub_put("/issues/1/", "update_issue") 75 | @issue=SentryApi.update_issue("1", {status: "resolved", assignedTo: "thierry.xing@gmail.com", hasSeen: true, isBookmarked: true, isSubscribed: true}) 76 | end 77 | 78 | it "should get the correct resource" do 79 | expect(a_put("/issues/1/")).to have_been_made 80 | end 81 | 82 | it "should return a response of issue" do 83 | expect(@issue.culprit).to eq("lib/tasks/device_task.rake in rescue in do_send_data at line 140") 84 | end 85 | end 86 | 87 | describe ".latest_event" do 88 | before do 89 | stub_get("/issues/1/events/latest/", "latest_event") 90 | @event = SentryApi.latest_event("1") 91 | end 92 | 93 | it "should get the correct resource" do 94 | expect(a_get("/issues/1/events/latest/")).to have_been_made 95 | end 96 | 97 | it "should return a response of event" do 98 | expect(@event.eventID).to eq("03fc4d526a3d47c7b2219ca1f3c81cf7") 99 | end 100 | end 101 | 102 | describe ".oldest_event" do 103 | before do 104 | stub_get("/issues/1/events/oldest/", "oldest_event") 105 | @event = SentryApi.oldest_event("1") 106 | end 107 | 108 | it "should get the correct resource" do 109 | expect(a_get("/issues/1/events/oldest/")).to have_been_made 110 | end 111 | 112 | it "should return a response of event" do 113 | expect(@event.eventID).to eq("03fc4d526a3d47c7b2219ca1f3c81cf7") 114 | end 115 | end 116 | 117 | end 118 | -------------------------------------------------------------------------------- /spec/sentry-api/client/issues_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryApi::Client do 4 | 5 | describe ".issues" do 6 | before do 7 | stub_get("/projects/sentry-sc/project-slug/issues/", "project_issues") 8 | @issues = SentryApi.issues("project-slug") 9 | end 10 | 11 | it "should get the correct resource" do 12 | expect(a_get("/projects/sentry-sc/project-slug/issues/")).to have_been_made 13 | end 14 | 15 | it "should return a array of issues" do 16 | expect(@issues.first.culprit).to eq("org.hsqldb.jdbc.Util in throwError") 17 | end 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /spec/sentry-api/client/organizations_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryApi::Client do 4 | 5 | describe ".organizations" do 6 | before do 7 | stub_get("/organizations/?member=false", "organizations") 8 | @organizations = SentryApi.organizations 9 | end 10 | 11 | it "should get the correct resource" do 12 | expect(a_get("/organizations/?member=false")).to have_been_made 13 | end 14 | 15 | it "should return a paginated response of organizations" do 16 | expect(@organizations).to be_a SentryApi::PaginatedResponse 17 | expect(@organizations.first.name).to eq("The Interstellar Jurisdiction") 18 | expect(@organizations.first.slug).to eq("the-interstellar-jurisdiction") 19 | end 20 | end 21 | 22 | describe ".organization_projects" do 23 | before do 24 | stub_get("/organizations/sentry-sc/projects/", "organization_projects") 25 | @projects = SentryApi.organization_projects 26 | end 27 | 28 | it "should get the correct resource" do 29 | expect(a_get("/organizations/sentry-sc/projects/")).to have_been_made 30 | end 31 | 32 | it "should return a paginated response of organization's projects" do 33 | expect(@projects).to be_a SentryApi::PaginatedResponse 34 | expect(@projects.first.status).to eq("active") 35 | expect(@projects.first.team.slug).to eq("powerful-abolitionist") 36 | end 37 | end 38 | 39 | describe ".organization" do 40 | before do 41 | stub_get("/organizations/sentry-sc/", "organization") 42 | @organization = SentryApi.organization 43 | end 44 | 45 | it "should get the correct resource" do 46 | expect(a_get("/organizations/sentry-sc/")).to have_been_made 47 | end 48 | 49 | it "should return a response of organization" do 50 | expect(@organization).to be_a SentryApi::ObjectifiedHash 51 | expect(@organization.slug).to eq("the-interstellar-jurisdiction") 52 | expect(@organization.teams.first["name"]).to eq("Ancient Gabelers") 53 | end 54 | end 55 | 56 | describe ".update_organization" do 57 | before do 58 | stub_put("/organizations/sentry-sc/", "update_organization") 59 | @edited_organization = SentryApi.update_organization({name: "Impeccably Designated"}) 60 | end 61 | 62 | it "should get the correct resource" do 63 | expect(a_put("/organizations/sentry-sc/")).to have_been_made 64 | end 65 | 66 | it "should return information about an update organization" do 67 | expect(@edited_organization.name).to eq("Impeccably Designated") 68 | end 69 | end 70 | 71 | describe ".organization_stats" do 72 | before do 73 | stub_get("/organizations/sentry-sc/stats/", "organization_stats").with(query: {stat: "received"}) 74 | @stats = SentryApi.organization_stats({stat: "received"}) 75 | end 76 | 77 | it "should get the correct resource" do 78 | expect(a_get("/organizations/sentry-sc/stats/").with(query: {stat: "received"})).to have_been_made 79 | end 80 | 81 | it "should return array about an organization's event counts" do 82 | expect(@stats[0][0]).to eq(1472158800) 83 | expect(@stats[0][1]).to eq(6330) 84 | end 85 | end 86 | 87 | describe ".create_team" do 88 | before do 89 | stub_post("/organizations/sentry-sc/teams/", "create_team").with(body: {name: "name"}) 90 | @team = SentryApi.create_team({name: "name"}) 91 | end 92 | 93 | it "should get the correct resource" do 94 | expect(a_post("/organizations/sentry-sc/teams/").with(body: {name: "name"})).to have_been_made 95 | end 96 | 97 | it "should return array about an organization's event counts" do 98 | expect(@team.slug).to eq("ancient-gabelers") 99 | end 100 | end 101 | 102 | end 103 | -------------------------------------------------------------------------------- /spec/sentry-api/client/projects_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryApi::Client do 4 | 5 | describe ".projects" do 6 | before do 7 | stub_get("/projects/", "projects") 8 | @projects = SentryApi.projects 9 | end 10 | 11 | it "should get the correct resource" do 12 | expect(a_get("/projects/")).to have_been_made 13 | end 14 | 15 | it "should return an array response of projects" do 16 | expect(@projects.first.slug).to eq("the-spoiled-yoghurt") 17 | end 18 | end 19 | 20 | describe ".project" do 21 | before do 22 | stub_get("/projects/sentry-sc/project-slug/", "project") 23 | @project=SentryApi.project("project-slug") 24 | end 25 | 26 | it "should get the correct resource" do 27 | expect(a_get("/projects/sentry-sc/project-slug/")).to have_been_made 28 | end 29 | 30 | it "should return a response of project" do 31 | expect(@project.name).to eq("Pump Station") 32 | end 33 | end 34 | 35 | describe ".update_project" do 36 | before do 37 | stub_put("/projects/sentry-sc/project-slug/", "update_project") 38 | @project=SentryApi.update_project("project-slug", {name: "Plane Proxy", slug: "plane-proxy", isBookmarked: true, options: {}}) 39 | end 40 | 41 | it "should get the correct resource" do 42 | expect(a_put("/projects/sentry-sc/project-slug/")).to have_been_made 43 | end 44 | 45 | it "should return a response of project" do 46 | expect(@project.name).to eq("Plane Proxy") 47 | end 48 | end 49 | 50 | describe ".delete_project" do 51 | before do 52 | stub_delete("/projects/sentry-sc/project-slug/", "delete_project") 53 | SentryApi.delete_project("project-slug") 54 | end 55 | 56 | it "should get the correct resource" do 57 | expect(a_delete("/projects/sentry-sc/project-slug/")).to have_been_made 58 | end 59 | end 60 | 61 | describe ".project_stats" do 62 | before do 63 | stub_get("/projects/sentry-sc/project-slug/stats/", "project_stats").with(query: {stat: "received"}) 64 | @stats = SentryApi.project_stats("project-slug", {stat: "received"}) 65 | end 66 | 67 | it "should get the correct resource" do 68 | expect(a_get("/projects/sentry-sc/project-slug/stats/").with(query: {stat: "received"})).to have_been_made 69 | end 70 | 71 | it "should return array about an project's event counts" do 72 | expect(@stats[0][0]).to eq(1472158800) 73 | expect(@stats[0][1]).to eq(1012) 74 | end 75 | end 76 | 77 | describe ".project_dsym_files" do 78 | before do 79 | stub_get("/projects/sentry-sc/project-slug/files/dsyms/", "project_dsym_files") 80 | @files = SentryApi.project_dsym_files("project-slug") 81 | end 82 | 83 | it "should get the correct resource" do 84 | expect(a_get("/projects/sentry-sc/project-slug/files/dsyms/")).to have_been_made 85 | end 86 | 87 | it "should return a response of result" do 88 | expect(@files.first.objectName).to eq("GMThirdParty") 89 | end 90 | end 91 | 92 | describe ".create_client_key" do 93 | before do 94 | stub_post("/projects/sentry-sc/project-slug/keys/", "create_client_key") 95 | @result = SentryApi.create_client_key("project-slug", "Fabulous Key") 96 | end 97 | 98 | it "should get the correct resource" do 99 | expect(a_post("/projects/sentry-sc/project-slug/keys/")).to have_been_made 100 | end 101 | 102 | it "should return a response of result" do 103 | expect(@result.label).to eq("Fabulous Key") 104 | end 105 | end 106 | 107 | describe ".delete_client_key" do 108 | before do 109 | stub_delete("/projects/sentry-sc/project-slug/keys/87c990582e07446b9907b357fc27730e/", "delete_client_key") 110 | SentryApi.delete_client_key("project-slug", "87c990582e07446b9907b357fc27730e") 111 | end 112 | 113 | it "should get the correct resource" do 114 | expect(a_delete("/projects/sentry-sc/project-slug/keys/87c990582e07446b9907b357fc27730e/")).to have_been_made 115 | end 116 | end 117 | 118 | describe ".client_keys" do 119 | before do 120 | stub_get("/projects/sentry-sc/project-slug/keys/", "client_keys") 121 | @keys = SentryApi.client_keys("project-slug") 122 | end 123 | 124 | it "should get the correct resource" do 125 | expect(a_get("/projects/sentry-sc/project-slug/keys/")).to have_been_made 126 | end 127 | 128 | it "should return a response of result" do 129 | expect(@keys.first.label).to eq("Fabulous Key") 130 | end 131 | end 132 | 133 | describe ".update_client_key" do 134 | before do 135 | stub_put("/projects/sentry-sc/project-slug/keys/b692781e8be347b98722d62506fc8aa8/", "update_client_key") 136 | @key = SentryApi.update_client_key("project-slug", "b692781e8be347b98722d62506fc8aa8", {name: "Quite Positive Key"}) 137 | end 138 | 139 | it "should get the correct resource" do 140 | expect(a_put("/projects/sentry-sc/project-slug/keys/b692781e8be347b98722d62506fc8aa8/")).to have_been_made 141 | end 142 | 143 | it "should return a response of result" do 144 | expect(@key.label).to eq("Quite Positive Key") 145 | end 146 | end 147 | 148 | describe ".project_events" do 149 | before do 150 | stub_get("/projects/sentry-sc/project-slug/events/", "project_events") 151 | @event = SentryApi.project_events("project-slug") 152 | end 153 | 154 | it "should get the correct resource" do 155 | expect(a_get("/projects/sentry-sc/project-slug/events/")).to have_been_made 156 | end 157 | 158 | it "should return an array response of events" do 159 | expect(@event.first.eventID).to eq("ef4e4e732d2544279851cf7c1b42716e") 160 | end 161 | end 162 | 163 | describe ".project_event" do 164 | before do 165 | stub_get("/projects/sentry-sc/project-slug/events/ef4e4e732d2544279851cf7c1b42716e/", "project_event") 166 | @event = SentryApi.project_event("project-slug", "ef4e4e732d2544279851cf7c1b42716e") 167 | end 168 | 169 | it "should get the correct resource" do 170 | expect(a_get("/projects/sentry-sc/project-slug/events/ef4e4e732d2544279851cf7c1b42716e/")).to have_been_made 171 | end 172 | 173 | it "should return a response of event" do 174 | expect(@event.eventID).to eq("ef4e4e732d2544279851cf7c1b42716e") 175 | end 176 | end 177 | 178 | describe ".project_issues" do 179 | before do 180 | stub_get("/projects/sentry-sc/project-slug/issues/", "project_issues") 181 | @issues = SentryApi.project_issues("project-slug") 182 | end 183 | 184 | it "should get the correct resource" do 185 | expect(a_get("/projects/sentry-sc/project-slug/issues/")).to have_been_made 186 | end 187 | 188 | it "should return a array of issues" do 189 | expect(@issues.first.culprit).to eq("org.hsqldb.jdbc.Util in throwError") 190 | end 191 | end 192 | 193 | end 194 | -------------------------------------------------------------------------------- /spec/sentry-api/client/releases_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryApi::Client do 4 | 5 | describe ".release" do 6 | before do 7 | stub_get("/projects/sentry-sc/project-slug/releases/1.0/", "release") 8 | @release = SentryApi.release("project-slug", "1.0") 9 | end 10 | 11 | it "should get the correct resource" do 12 | expect(a_get("/projects/sentry-sc/project-slug/releases/1.0/")).to have_been_made 13 | end 14 | 15 | it "should return a response of release" do 16 | expect(@release.version).to eq("6945297bf064ca1ce68cfba53fbb91e2ef47eec6") 17 | end 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /spec/sentry-api/client/teams_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryApi::Client do 4 | 5 | describe ".team" do 6 | before do 7 | stub_get("/teams/sentry-sc/team-slug/", "team") 8 | @team = SentryApi.team("team-slug") 9 | end 10 | 11 | it "should get the correct resource" do 12 | expect(a_get("/teams/sentry-sc/team-slug/")).to have_been_made 13 | end 14 | 15 | it "should return an response of team" do 16 | expect(@team.slug).to eq("powerful-abolitionist") 17 | end 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /spec/sentry-api/error_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe SentryApi::Error do 4 | describe "#handle_message" do 5 | require "stringio" 6 | 7 | before do 8 | request_object = HTTParty::Request.new(Net::HTTP::Get, '/') 9 | response_object = Net::HTTPOK.new('1.1', 200, 'OK') 10 | body = StringIO.new("{foo:'bar'}") 11 | 12 | def body.message; 13 | self.string; 14 | end 15 | 16 | parsed_response = lambda { body } 17 | response_object['last-modified'] = Date.new(2016, 1, 15).to_s 18 | response_object['content-length'] = "1024" 19 | 20 | response = HTTParty::Response.new(request_object, response_object, parsed_response, body: body) 21 | @error = SentryApi::Error::ResponseError.new(response) 22 | 23 | @array = Array.new(['First message.', 'Second message.']) 24 | @obj_h = SentryApi::ObjectifiedHash.new(user: ['not set'], 25 | password: ['too short'], 26 | embed_entity: {foo: ['bar'], sna: ['fu']}) 27 | end 28 | 29 | context "when passed an ObjectifiedHash" do 30 | it "should return a joined string of error messages sorted by key" do 31 | expect(@error.send(:handle_message, @obj_h)).to eq("'embed_entity' (foo: bar) (sna: fu), 'password' too short, 'user' not set") 32 | end 33 | end 34 | 35 | context "when passed an Array" do 36 | it "should return a joined string of messages" do 37 | expect(@error.send(:handle_message, @array)).to eq("First message. Second message.") 38 | end 39 | end 40 | 41 | context "when passed a String" do 42 | it "should return the String untouched" do 43 | error = 'this is an error string' 44 | expect(@error.send(:handle_message, error)).to eq('this is an error string') 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/sentry-api/objectified_hash_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryApi::ObjectifiedHash do 4 | before do 5 | @hash = {a: 1, b: 2, 'string' => 'string', symbol: :symbol} 6 | @oh = SentryApi::ObjectifiedHash.new @hash 7 | end 8 | 9 | it "should objectify hash" do 10 | expect(@oh.a).to eq(@hash[:a]) 11 | expect(@oh.b).to eq(@hash[:b]) 12 | end 13 | 14 | describe "#to_hash" do 15 | it "should return an original hash" do 16 | expect(@oh.to_hash).to eq(@hash) 17 | end 18 | 19 | it "should have an alias #to_h" do 20 | expect(@oh.respond_to?(:to_h)).to be_truthy 21 | end 22 | end 23 | 24 | describe "#inspect" do 25 | it "should return a formatted string" do 26 | pretty_string = "#<#{@oh.class.name}:#{@oh.object_id} {hash: #{@hash}}" 27 | expect(@oh.inspect).to eq(pretty_string) 28 | end 29 | end 30 | 31 | describe "#respond_to" do 32 | it "should return true for methods this object responds to through method_missing as sym" do 33 | expect(@oh.respond_to?(:a)).to be_truthy 34 | end 35 | 36 | it "should return true for methods this object responds to through method_missing as string" do 37 | expect(@oh.respond_to?('string')).to be_truthy 38 | end 39 | 40 | it "should not care if you use a string or symbol to reference a method" do 41 | expect(@oh.respond_to?(:string)).to be_truthy 42 | end 43 | 44 | it "should not care if you use a string or symbol to reference a method" do 45 | expect(@oh.respond_to?('symbol')).to be_truthy 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/sentry-api/page_links_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryApi::PageLinks do 4 | before do 5 | @page_links = SentryApi::PageLinks.new('Link' => "; rel=\"previous\"; results=\"false\"; cursor=\"100:-1:1\", ; rel=\"next\"; results=\"true\"; cursor=\"100:1:0\"") 6 | end 7 | 8 | context '.extract_links' do 9 | it 'should extract link header appropriately' do 10 | expect(@page_links.next).to eql 'http://example.com/api/0/organizations/?&cursor=100:1:0' 11 | expect(@page_links.previous).to be_nil 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/sentry-api/paginated_response_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryApi::PaginatedResponse do 4 | before do 5 | array = [1, 2, 3, 4] 6 | @paginated_response = SentryApi::PaginatedResponse.new array 7 | end 8 | 9 | it "should respond to *_page and has_*_page methods" do 10 | expect(@paginated_response).to respond_to :next_page 11 | expect(@paginated_response).to respond_to :prev_page 12 | expect(@paginated_response).to respond_to :has_next_page? 13 | expect(@paginated_response).to respond_to :has_prev_page? 14 | end 15 | 16 | context '.parse_headers!' do 17 | it "should parse headers" do 18 | @paginated_response.parse_headers!('Link' => "; rel=\"previous\"; results=\"false\"; cursor=\"100:-1:1\", ; rel=\"next\"; results=\"true\"; cursor=\"100:1:0\"") 19 | client = @paginated_response.client = double('client') 20 | allow(client).to receive(:endpoint).and_return("http://example.com/api/0") 21 | expect(@paginated_response.has_next_page?).to be true 22 | expect(@paginated_response.has_prev_page?).to be false 23 | end 24 | end 25 | 26 | context '.each_page' do 27 | it "should iterate pages" do 28 | next_page = double('next_page') 29 | allow(@paginated_response).to receive(:has_next_page?).and_return(true) 30 | allow(@paginated_response).to receive(:next_page).and_return(next_page) 31 | allow(next_page).to receive(:has_next_page?).and_return(false) 32 | expect { |b| @paginated_response.each_page(&b) }.to yield_successive_args(@paginated_response, next_page) 33 | end 34 | end 35 | 36 | context '.auto_paginate' do 37 | it "should returns an array if block is not given" do 38 | next_page = double('next_page') 39 | allow(@paginated_response).to receive(:has_next_page?).and_return(true) 40 | allow(@paginated_response).to receive(:next_page).and_return(next_page) 41 | allow(next_page).to receive(:has_next_page?).and_return(false) 42 | allow(next_page).to receive(:to_ary).and_return([5, 6, 7, 8]) 43 | expect(@paginated_response.auto_paginate).to contain_exactly(1, 2, 3, 4, 5, 6, 7, 8) 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/sentry-api/request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryApi::Request do 4 | it { should respond_to :get } 5 | it { should respond_to :post } 6 | it { should respond_to :put } 7 | it { should respond_to :delete } 8 | before do 9 | @request = SentryApi::Request.new 10 | end 11 | 12 | describe ".default_options" do 13 | it "should have default values" do 14 | default_options = SentryApi::Request.default_options 15 | expect(default_options).to be_a Hash 16 | expect(default_options[:parser]).to be_a Proc 17 | expect(default_options[:format]).to eq(:json) 18 | expect(default_options[:headers]).to eq('Content-Type' => 'application/json') 19 | expect(default_options[:default_params]).to be_nil 20 | end 21 | end 22 | 23 | describe ".parse" do 24 | it "should return ObjectifiedHash" do 25 | body = JSON.unparse(a: 1, b: 2) 26 | expect(SentryApi::Request.parse(body)).to be_an SentryApi::ObjectifiedHash 27 | expect(SentryApi::Request.parse("true")).to be true 28 | expect(SentryApi::Request.parse("false")).to be false 29 | 30 | expect { SentryApi::Request.parse("string") }.to raise_error(SentryApi::Error::Parsing) 31 | end 32 | end 33 | 34 | describe "#set_request_defaults" do 35 | context "when endpoint is not set" do 36 | it "should raise Error::MissingCredentials" do 37 | @request.endpoint = nil 38 | expect do 39 | @request.set_request_defaults 40 | end.to raise_error(SentryApi::Error::MissingCredentials, 'Please set an endpoint to API') 41 | end 42 | end 43 | 44 | context "when endpoint is set" do 45 | before(:each) do 46 | @request.endpoint = 'http://rabbit-hole.example.org' 47 | end 48 | 49 | it "should set default_params" do 50 | @request.set_request_defaults 51 | expect(SentryApi::Request.default_params).to eq({}) 52 | end 53 | end 54 | end 55 | 56 | describe "#set_authorization_header" do 57 | it "should set the correct header when setting an auth_token" do 58 | @request.auth_token = '3225e2804d31fea13fc41fc83bffef00cfaedc463118646b154acc6f94747603' 59 | expect(@request.send(:set_authorization_header, {})).to eq("Authorization" => "Bearer 3225e2804d31fea13fc41fc83bffef00cfaedc463118646b154acc6f94747603") 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/sentry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryApi do 4 | after { SentryApi.reset } 5 | 6 | describe ".client" do 7 | it "should be a SentryApi::Client" do 8 | expect(SentryApi.client).to be_a SentryApi::Client 9 | end 10 | 11 | it "should not override each other" do 12 | client1 = SentryApi.client(endpoint: 'https://api1.example.com', auth_token: '001') 13 | client2 = SentryApi.client(endpoint: 'https://api2.example.com', auth_token: '002') 14 | expect(client1.endpoint).to eq('https://api1.example.com') 15 | expect(client2.endpoint).to eq('https://api2.example.com') 16 | expect(client1.auth_token).to eq('001') 17 | expect(client2.auth_token).to eq('002') 18 | end 19 | 20 | it "should set auth_token to the auth_token when provided" do 21 | client = SentryApi.client(endpoint: 'https://api2.example.com', auth_token: '5b0c2c44939d4ea3b2f85a101d6495c8a085d2134eb542158235309606d0297e') 22 | expect(client.auth_token).to eq('5b0c2c44939d4ea3b2f85a101d6495c8a085d2134eb542158235309606d0297e') 23 | end 24 | end 25 | 26 | describe ".actions" do 27 | it "should return an array of client methods" do 28 | actions = SentryApi.actions 29 | expect(actions).to be_an Array 30 | expect(actions.first).to be_a Symbol 31 | end 32 | end 33 | 34 | describe ".endpoint=" do 35 | it "should set endpoint" do 36 | SentryApi.endpoint = 'https://api.example.com' 37 | expect(SentryApi.endpoint).to eq('https://api.example.com') 38 | end 39 | end 40 | 41 | describe ".org_slug=" do 42 | it "should set org_slug" do 43 | SentryApi.default_org_slug = 'sentry-sc' 44 | expect(SentryApi.default_org_slug).to eq('sentry-sc') 45 | end 46 | end 47 | 48 | describe ".auth_token=" do 49 | it "should set auth_token", focus: true do 50 | SentryApi.auth_token = '5b0c2c44939d4ea3b2f85a101d6495c8a085d2134eb542158235309606d0297e' 51 | expect(SentryApi.auth_token).to eq('5b0c2c44939d4ea3b2f85a101d6495c8a085d2134eb542158235309606d0297e') 52 | end 53 | end 54 | 55 | describe ".configure" do 56 | SentryApi::Configuration::VALID_OPTIONS_KEYS.each do |key| 57 | it "should set #{key}" do 58 | SentryApi.configure do |config| 59 | config.send("#{key}=", key) 60 | expect(SentryApi.send(key)).to eq(key) 61 | end 62 | end 63 | end 64 | end 65 | 66 | describe ".http_proxy" do 67 | it "delegates the method to Sentry::Request" do 68 | SentryApi.endpoint = 'https://api.example.com' 69 | request = class_spy(SentryApi::Request).as_stubbed_const 70 | 71 | SentryApi.http_proxy('fazbearentertainment.com', 1987, 'ffazbear', 'itsme') 72 | expect(request).to have_received(:http_proxy). 73 | with('fazbearentertainment.com', 1987, 'ffazbear', 'itsme') 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'webmock/rspec' 3 | 4 | require File.expand_path('../../lib/sentry-api', __FILE__) 5 | 6 | def capture_output 7 | out = StringIO.new 8 | $stdout = out 9 | $stderr = out 10 | yield 11 | $stdout = STDOUT 12 | $stderr = STDERR 13 | out.string 14 | end 15 | 16 | def load_fixture(name) 17 | File.new(File.dirname(__FILE__) + "/fixtures/#{name}.json") 18 | end 19 | 20 | RSpec.configure do |config| 21 | config.before(:all) do 22 | SentryApi.endpoint = 'https://api.example.com/api/0' 23 | SentryApi.auth_token = 'secret' 24 | SentryApi.default_org_slug = 'sentry-sc' 25 | end 26 | end 27 | 28 | # GET 29 | def stub_get(path, fixture, status_code=200) 30 | stub_request(:get, "#{SentryApi.endpoint}#{path}"). 31 | with(:headers => {'Authorization' => "Bearer #{SentryApi.auth_token}", 'Content-Type' => 'application/json'}). 32 | to_return(body: load_fixture(fixture), status: status_code) 33 | end 34 | 35 | def a_get(path) 36 | a_request(:get, "#{SentryApi.endpoint}#{path}"). 37 | with(:headers => {'Authorization' => "Bearer #{SentryApi.auth_token}", 'Content-Type' => 'application/json'}) 38 | end 39 | 40 | # POST 41 | def stub_post(path, fixture, status_code=200) 42 | stub_request(:post, "#{SentryApi.endpoint}#{path}"). 43 | with(:headers => {'Authorization' => "Bearer #{SentryApi.auth_token}", 'Content-Type' => 'application/json'}). 44 | to_return(body: load_fixture(fixture), status: status_code) 45 | end 46 | 47 | def a_post(path) 48 | a_request(:post, "#{SentryApi.endpoint}#{path}"). 49 | with(:headers => {'Authorization' => "Bearer #{SentryApi.auth_token}", 'Content-Type' => 'application/json'}) 50 | end 51 | 52 | # PUT 53 | def stub_put(path, fixture) 54 | stub_request(:put, "#{SentryApi.endpoint}#{path}"). 55 | with(:headers => {'Authorization' => "Bearer #{SentryApi.auth_token}", 'Content-Type' => 'application/json'}). 56 | to_return(body: load_fixture(fixture)) 57 | end 58 | 59 | def a_put(path) 60 | a_request(:put, "#{SentryApi.endpoint}#{path}"). 61 | with(:headers => {'Authorization' => "Bearer #{SentryApi.auth_token}", 'Content-Type' => 'application/json'}) 62 | end 63 | 64 | # DELETE 65 | def stub_delete(path, fixture) 66 | stub_request(:delete, "#{SentryApi.endpoint}#{path}"). 67 | with(:headers => {'Authorization' => "Bearer #{SentryApi.auth_token}", 'Content-Type' => 'application/json'}). 68 | to_return(body: load_fixture(fixture)) 69 | end 70 | 71 | def a_delete(path) 72 | a_request(:delete, "#{SentryApi.endpoint}#{path}"). 73 | with(:headers => {'Authorization' => "Bearer #{SentryApi.auth_token}", 'Content-Type' => 'application/json'}) 74 | end 75 | --------------------------------------------------------------------------------