diff options
-rw-r--r-- | app/assets/javascripts/raven/index.js | 16 | ||||
-rw-r--r-- | app/assets/javascripts/raven/raven_config.js | 100 | ||||
-rw-r--r-- | app/controllers/admin/application_settings_controller.rb | 2 | ||||
-rw-r--r-- | app/models/application_setting.rb | 4 | ||||
-rw-r--r-- | app/views/admin/application_settings/_form.html.haml | 15 | ||||
-rw-r--r-- | app/views/layouts/_head.html.haml | 5 | ||||
-rw-r--r-- | app/views/layouts/application.html.haml | 2 | ||||
-rw-r--r-- | app/views/layouts/devise.html.haml | 1 | ||||
-rw-r--r-- | app/views/layouts/devise_empty.html.haml | 1 | ||||
-rw-r--r-- | config/application.rb | 3 | ||||
-rw-r--r-- | config/webpack.config.js | 1 | ||||
-rw-r--r-- | db/migrate/20170428123910_add_clientside_sentry_to_application_settings.rb | 12 | ||||
-rw-r--r-- | db/schema.rb | 4 | ||||
-rw-r--r-- | lib/api/settings.rb | 5 | ||||
-rw-r--r-- | lib/api/v3/settings.rb | 6 | ||||
-rw-r--r-- | lib/gitlab/gon_helper.rb | 3 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | spec/features/raven_js_spec.rb | 23 | ||||
-rw-r--r-- | spec/javascripts/raven/index_spec.js | 41 | ||||
-rw-r--r-- | spec/javascripts/raven/raven_config_spec.js | 280 | ||||
-rw-r--r-- | yarn.lock | 8 |
21 files changed, 526 insertions, 7 deletions
diff --git a/app/assets/javascripts/raven/index.js b/app/assets/javascripts/raven/index.js new file mode 100644 index 00000000000..3c5656040b9 --- /dev/null +++ b/app/assets/javascripts/raven/index.js @@ -0,0 +1,16 @@ +import RavenConfig from './raven_config'; + +const index = function index() { + RavenConfig.init({ + sentryDsn: gon.sentry_dsn, + currentUserId: gon.current_user_id, + whitelistUrls: [gon.gitlab_url], + isProduction: gon.is_production, + }); + + return RavenConfig; +}; + +index(); + +export default index; diff --git a/app/assets/javascripts/raven/raven_config.js b/app/assets/javascripts/raven/raven_config.js new file mode 100644 index 00000000000..43e55bc5415 --- /dev/null +++ b/app/assets/javascripts/raven/raven_config.js @@ -0,0 +1,100 @@ +import Raven from 'raven-js'; + +const IGNORE_ERRORS = [ + // Random plugins/extensions + 'top.GLOBALS', + // See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error. html + 'originalCreateNotification', + 'canvas.contentDocument', + 'MyApp_RemoveAllHighlights', + 'http://tt.epicplay.com', + 'Can\'t find variable: ZiteReader', + 'jigsaw is not defined', + 'ComboSearch is not defined', + 'http://loading.retry.widdit.com/', + 'atomicFindClose', + // Facebook borked + 'fb_xd_fragment', + // ISP "optimizing" proxy - `Cache-Control: no-transform` seems to + // reduce this. (thanks @acdha) + // See http://stackoverflow.com/questions/4113268 + 'bmi_SafeAddOnload', + 'EBCallBackMessageReceived', + // See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx + 'conduitPage', +]; + +const IGNORE_URLS = [ + // Facebook flakiness + /graph\.facebook\.com/i, + // Facebook blocked + /connect\.facebook\.net\/en_US\/all\.js/i, + // Woopra flakiness + /eatdifferent\.com\.woopra-ns\.com/i, + /static\.woopra\.com\/js\/woopra\.js/i, + // Chrome extensions + /extensions\//i, + /^chrome:\/\//i, + // Other plugins + /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb + /webappstoolbarba\.texthelp\.com\//i, + /metrics\.itunes\.apple\.com\.edgesuite\.net\//i, +]; + +const SAMPLE_RATE = 95; + +const RavenConfig = { + IGNORE_ERRORS, + IGNORE_URLS, + SAMPLE_RATE, + init(options = {}) { + this.options = options; + + this.configure(); + this.bindRavenErrors(); + if (this.options.currentUserId) this.setUser(); + }, + + configure() { + Raven.config(this.options.sentryDsn, { + whitelistUrls: this.options.whitelistUrls, + environment: this.options.isProduction ? 'production' : 'development', + ignoreErrors: this.IGNORE_ERRORS, + ignoreUrls: this.IGNORE_URLS, + shouldSendCallback: this.shouldSendSample.bind(this), + }).install(); + }, + + setUser() { + Raven.setUserContext({ + id: this.options.currentUserId, + }); + }, + + bindRavenErrors() { + window.$(document).on('ajaxError.raven', this.handleRavenErrors); + }, + + handleRavenErrors(event, req, config, err) { + const error = err || req.statusText; + const responseText = req.responseText || 'Unknown response text'; + + Raven.captureMessage(error, { + extra: { + type: config.type, + url: config.url, + data: config.data, + status: req.status, + response: responseText.substring(0, 100), + error, + event, + }, + }); + }, + + shouldSendSample() { + return Math.random() * 100 <= this.SAMPLE_RATE; + }, +}; + +export default RavenConfig; diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 643993d035e..152d7baad49 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -133,6 +133,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :signup_enabled, :sentry_dsn, :sentry_enabled, + :clientside_sentry_dsn, + :clientside_sentry_enabled, :send_user_confirmation_email, :shared_runners_enabled, :shared_runners_text, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index cf042717c95..54f01f8637e 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -62,6 +62,10 @@ class ApplicationSetting < ActiveRecord::Base presence: true, if: :sentry_enabled + validates :clientside_sentry_dsn, + presence: true, + if: :clientside_sentry_enabled + validates :akismet_api_key, presence: true, if: :akismet_enabled diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 0dc1103eece..6030c8b1dfa 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -411,6 +411,21 @@ .col-sm-10 = f.text_field :sentry_dsn, class: 'form-control' + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :clientside_sentry_enabled do + = f.check_box :clientside_sentry_enabled + Enable Clientside Sentry + .help-block + Sentry can also be used for reporting and logging clientside exceptions. + %a{ href: 'https://sentry.io/for/javascript/', target: '_blank', rel: 'noopener noreferrer' } https://sentry.io/for/javascript/ + + .form-group + = f.label :clientside_sentry_dsn, 'Clientside Sentry DSN', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :clientside_sentry_dsn, class: 'form-control' + %fieldset %legend Repository Storage .form-group diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 19473b6ab27..fa4d760a20e 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -28,9 +28,14 @@ = stylesheet_link_tag "application", media: "all" = stylesheet_link_tag "print", media: "print" + = Gon::Base.render_data + = webpack_bundle_tag "runtime" = webpack_bundle_tag "common" = webpack_bundle_tag "main" + = webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled + + = current_application_settings.clientside_sentry_enabled - if content_for?(:page_specific_javascripts) = yield :page_specific_javascripts diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 36543edc040..4c7f0b57d16 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -2,8 +2,6 @@ %html{ lang: "en", class: "#{page_class}" } = render "layouts/head" %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } } - = Gon::Base.render_data - = render "layouts/header/default", title: header_title = render 'layouts/page', sidebar: sidebar, nav: nav diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 3368a9beb29..52fb46eb8c9 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -3,7 +3,6 @@ = render "layouts/head" %body.ui_charcoal.login-page.application.navless{ data: { page: body_data_page } } .page-wrap - = Gon::Base.render_data = render "layouts/header/empty" = render "layouts/broadcast" .container.navless-container diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml index 7466423a934..ed6731bde95 100644 --- a/app/views/layouts/devise_empty.html.haml +++ b/app/views/layouts/devise_empty.html.haml @@ -2,7 +2,6 @@ %html{ lang: "en" } = render "layouts/head" %body.ui_charcoal.login-page.application.navless - = Gon::Base.render_data = render "layouts/header/empty" = render "layouts/broadcast" .container.navless-container diff --git a/config/application.rb b/config/application.rb index f2ecc4ce77c..46652637c1f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -55,6 +55,7 @@ module Gitlab # - Webhook URLs (:hook) # - GitLab-shell secret token (:secret_token) # - Sentry DSN (:sentry_dsn) + # - Clientside Sentry DSN (:clientside_sentry_dsn) # - Deploy keys (:key) config.filter_parameters += %i( authentication_token @@ -71,6 +72,8 @@ module Gitlab runners_token secret_token sentry_dsn + clientside_sentry_enabled + clientside_sentry_dsn variables ) diff --git a/config/webpack.config.js b/config/webpack.config.js index 0ec9e48845e..ea699b1ecba 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -52,6 +52,7 @@ var config = { terminal: './terminal/terminal_bundle.js', u2f: ['vendor/u2f'], users: './users/users_bundle.js', + raven: './raven/index.js', }, output: { diff --git a/db/migrate/20170428123910_add_clientside_sentry_to_application_settings.rb b/db/migrate/20170428123910_add_clientside_sentry_to_application_settings.rb new file mode 100644 index 00000000000..380060b18b3 --- /dev/null +++ b/db/migrate/20170428123910_add_clientside_sentry_to_application_settings.rb @@ -0,0 +1,12 @@ +# rubocop:disable all +class AddClientsideSentryToApplicationSettings < ActiveRecord::Migration + DOWNTIME = true + DOWNTIME_REASON = 'This migration requires downtime because we must add 2 new columns, 1 of which has a default value.' + + def change + change_table :application_settings do |t| + t.boolean :clientside_sentry_enabled, default: false + t.string :clientside_sentry_dsn + end + end +end diff --git a/db/schema.rb b/db/schema.rb index be6684f3a6b..c3fbb74e878 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170426181740) do +ActiveRecord::Schema.define(version: 20170428123910) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -121,6 +121,8 @@ ActiveRecord::Schema.define(version: 20170426181740) do t.integer "cached_markdown_version" t.boolean "usage_ping_enabled", default: true, null: false t.string "uuid" + t.boolean "clientside_sentry_enabled", default: false + t.string "clientside_sentry_dsn" end create_table "audit_events", force: :cascade do |t| diff --git a/lib/api/settings.rb b/lib/api/settings.rb index d01c7f2703b..82f513c984e 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -58,6 +58,7 @@ module API :restricted_visibility_levels, :send_user_confirmation_email, :sentry_enabled, + :clientside_sentry_enabled, :session_expire_delay, :shared_runners_enabled, :sidekiq_throttling_enabled, @@ -138,6 +139,10 @@ module API given sentry_enabled: ->(val) { val } do requires :sentry_dsn, type: String, desc: 'Sentry Data Source Name' end + optional :clientside_sentry_enabled, type: Boolean, desc: 'Sentry can also be used for reporting and logging clientside exceptions. https://sentry.io/for/javascript/' + given clientside_sentry_enabled: ->(val) { val } do + requires :clientside_sentry_dsn, type: String, desc: 'Clientside Sentry Data Source Name' + end optional :repository_storage, type: String, desc: 'Storage paths for new projects' optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues." optional :koding_enabled, type: Boolean, desc: 'Enable Koding' diff --git a/lib/api/v3/settings.rb b/lib/api/v3/settings.rb index 748d6b97d4f..67a85e13d8c 100644 --- a/lib/api/v3/settings.rb +++ b/lib/api/v3/settings.rb @@ -89,6 +89,10 @@ module API given sentry_enabled: ->(val) { val } do requires :sentry_dsn, type: String, desc: 'Sentry Data Source Name' end + optional :clientside_sentry_enabled, type: Boolean, desc: 'Sentry can also be used for reporting and logging clientside exceptions. https://sentry.io/for/javascript/' + given clientside_sentry_enabled: ->(val) { val } do + requires :clientside_sentry_dsn, type: String, desc: 'Clientside Sentry Data Source Name' + end optional :repository_storage, type: String, desc: 'Storage paths for new projects' optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues." optional :koding_enabled, type: Boolean, desc: 'Enable Koding' @@ -120,7 +124,7 @@ module API :home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text, :shared_runners_enabled, :max_artifacts_size, :max_pages_size, :container_registry_token_expire_delay, :metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled, - :akismet_enabled, :admin_notification_email, :sentry_enabled, + :akismet_enabled, :admin_notification_email, :sentry_enabled, :clientside_sentry_enabled, :repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled, :version_check_enabled, :email_author_in_body, :html_emails_enabled, :housekeeping_enabled, :terminal_max_session_time diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 5ab84266b7d..848b3352c63 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -10,6 +10,9 @@ module Gitlab gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class gon.katex_css_url = ActionController::Base.helpers.asset_path('katex.css') gon.katex_js_url = ActionController::Base.helpers.asset_path('katex.js') + gon.sentry_dsn = current_application_settings.clientside_sentry_dsn if current_application_settings.clientside_sentry_enabled + gon.gitlab_url = Gitlab.config.gitlab.url + gon.is_production = Rails.env.production? if current_user gon.current_user_id = current_user.id diff --git a/package.json b/package.json index 9ed5e1a7475..d98d191a681 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "pikaday": "^1.5.1", "prismjs": "^1.6.0", "raphael": "^2.2.7", + "raven-js": "^3.14.0", "raw-loader": "^0.5.1", "react-dev-utils": "^0.5.2", "select2": "3.5.2-browserify", diff --git a/spec/features/raven_js_spec.rb b/spec/features/raven_js_spec.rb new file mode 100644 index 00000000000..e8fa49c18cb --- /dev/null +++ b/spec/features/raven_js_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +feature 'RavenJS', :feature, :js do + let(:raven_path) { '/raven.bundle.js' } + + it 'should not load raven if sentry is disabled' do + visit new_user_session_path + + expect(has_requested_raven).to eq(false) + end + + it 'should load raven if sentry is enabled' do + stub_application_setting(clientside_sentry_dsn: 'https://key@domain.com/id', clientside_sentry_enabled: true) + + visit new_user_session_path + + expect(has_requested_raven).to eq(true) + end + + def has_requested_raven + page.driver.network_traffic.one? {|request| request.url.end_with?(raven_path)} + end +end diff --git a/spec/javascripts/raven/index_spec.js b/spec/javascripts/raven/index_spec.js new file mode 100644 index 00000000000..85ec1de4e4e --- /dev/null +++ b/spec/javascripts/raven/index_spec.js @@ -0,0 +1,41 @@ +import RavenConfig from '~/raven/raven_config'; +import index from '~/raven/index'; + +describe('RavenConfig options', () => { + let sentryDsn; + let currentUserId; + let gitlabUrl; + let isProduction; + let indexReturnValue; + + beforeEach(() => { + sentryDsn = 'sentryDsn'; + currentUserId = 'currentUserId'; + gitlabUrl = 'gitlabUrl'; + isProduction = 'isProduction'; + + window.gon = { + sentry_dsn: sentryDsn, + current_user_id: currentUserId, + gitlab_url: gitlabUrl, + is_production: isProduction, + }; + + spyOn(RavenConfig, 'init'); + + indexReturnValue = index(); + }); + + it('should init with .sentryDsn, .currentUserId, .whitelistUrls and .isProduction', () => { + expect(RavenConfig.init).toHaveBeenCalledWith({ + sentryDsn, + currentUserId, + whitelistUrls: [gitlabUrl], + isProduction, + }); + }); + + it('should return RavenConfig', () => { + expect(indexReturnValue).toBe(RavenConfig); + }); +}); diff --git a/spec/javascripts/raven/raven_config_spec.js b/spec/javascripts/raven/raven_config_spec.js new file mode 100644 index 00000000000..b4c35420f90 --- /dev/null +++ b/spec/javascripts/raven/raven_config_spec.js @@ -0,0 +1,280 @@ +import Raven from 'raven-js'; +import RavenConfig from '~/raven/raven_config'; + +describe('RavenConfig', () => { + describe('IGNORE_ERRORS', () => { + it('should be an array of strings', () => { + const areStrings = RavenConfig.IGNORE_ERRORS.every(error => typeof error === 'string'); + + expect(areStrings).toBe(true); + }); + }); + + describe('IGNORE_URLS', () => { + it('should be an array of regexps', () => { + const areRegExps = RavenConfig.IGNORE_URLS.every(url => url instanceof RegExp); + + expect(areRegExps).toBe(true); + }); + }); + + describe('SAMPLE_RATE', () => { + it('should be a finite number', () => { + expect(typeof RavenConfig.SAMPLE_RATE).toEqual('number'); + }); + }); + + describe('init', () => { + let options; + + beforeEach(() => { + options = { + sentryDsn: '//sentryDsn', + ravenAssetUrl: '//ravenAssetUrl', + currentUserId: 1, + whitelistUrls: ['//gitlabUrl'], + isProduction: true, + }; + + spyOn(RavenConfig, 'configure'); + spyOn(RavenConfig, 'bindRavenErrors'); + spyOn(RavenConfig, 'setUser'); + + RavenConfig.init(options); + }); + + it('should set the options property', () => { + expect(RavenConfig.options).toEqual(options); + }); + + it('should call the configure method', () => { + expect(RavenConfig.configure).toHaveBeenCalled(); + }); + + it('should call the error bindings method', () => { + expect(RavenConfig.bindRavenErrors).toHaveBeenCalled(); + }); + + it('should call setUser', () => { + expect(RavenConfig.setUser).toHaveBeenCalled(); + }); + + it('should not call setUser if there is no current user ID', () => { + RavenConfig.setUser.calls.reset(); + + RavenConfig.init({ + sentryDsn: '//sentryDsn', + ravenAssetUrl: '//ravenAssetUrl', + currentUserId: undefined, + whitelistUrls: ['//gitlabUrl'], + isProduction: true, + }); + + expect(RavenConfig.setUser).not.toHaveBeenCalled(); + }); + }); + + describe('configure', () => { + let options; + let raven; + let ravenConfig; + + beforeEach(() => { + options = { + sentryDsn: '//sentryDsn', + whitelistUrls: ['//gitlabUrl'], + isProduction: true, + }; + + ravenConfig = jasmine.createSpyObj('ravenConfig', ['shouldSendSample']); + raven = jasmine.createSpyObj('raven', ['install']); + + spyOn(Raven, 'config').and.returnValue(raven); + + ravenConfig.options = options; + ravenConfig.IGNORE_ERRORS = 'ignore_errors'; + ravenConfig.IGNORE_URLS = 'ignore_urls'; + + RavenConfig.configure.call(ravenConfig); + }); + + it('should call Raven.config', () => { + expect(Raven.config).toHaveBeenCalledWith(options.sentryDsn, { + whitelistUrls: options.whitelistUrls, + environment: 'production', + ignoreErrors: ravenConfig.IGNORE_ERRORS, + ignoreUrls: ravenConfig.IGNORE_URLS, + shouldSendCallback: jasmine.any(Function), + }); + }); + + it('should call Raven.install', () => { + expect(raven.install).toHaveBeenCalled(); + }); + + it('should set .environment to development if isProduction is false', () => { + ravenConfig.options.isProduction = false; + + RavenConfig.configure.call(ravenConfig); + + expect(Raven.config).toHaveBeenCalledWith(options.sentryDsn, { + whitelistUrls: options.whitelistUrls, + environment: 'development', + ignoreErrors: ravenConfig.IGNORE_ERRORS, + ignoreUrls: ravenConfig.IGNORE_URLS, + shouldSendCallback: jasmine.any(Function), + }); + }); + }); + + describe('setUser', () => { + let ravenConfig; + + beforeEach(() => { + ravenConfig = { options: { currentUserId: 1 } }; + spyOn(Raven, 'setUserContext'); + + RavenConfig.setUser.call(ravenConfig); + }); + + it('should call .setUserContext', function () { + expect(Raven.setUserContext).toHaveBeenCalledWith({ + id: ravenConfig.options.currentUserId, + }); + }); + }); + + describe('bindRavenErrors', () => { + let $document; + let $; + + beforeEach(() => { + $document = jasmine.createSpyObj('$document', ['on']); + $ = jasmine.createSpy('$').and.returnValue($document); + + window.$ = $; + + RavenConfig.bindRavenErrors(); + }); + + it('should query for document using jquery', () => { + expect(window.$).toHaveBeenCalledWith(document); + }); + + it('should call .on', function () { + expect($document.on).toHaveBeenCalledWith('ajaxError.raven', RavenConfig.handleRavenErrors); + }); + }); + + describe('handleRavenErrors', () => { + let event; + let req; + let config; + let err; + + beforeEach(() => { + event = {}; + req = { status: 'status', responseText: 'responseText', statusText: 'statusText' }; + config = { type: 'type', url: 'url', data: 'data' }; + err = {}; + + spyOn(Raven, 'captureMessage'); + + RavenConfig.handleRavenErrors(event, req, config, err); + }); + + it('should call Raven.captureMessage', () => { + expect(Raven.captureMessage).toHaveBeenCalledWith(err, { + extra: { + type: config.type, + url: config.url, + data: config.data, + status: req.status, + response: req.responseText.substring(0, 100), + error: err, + event, + }, + }); + }); + + describe('if no err is provided', () => { + beforeEach(() => { + Raven.captureMessage.calls.reset(); + + RavenConfig.handleRavenErrors(event, req, config); + }); + + it('should use req.statusText as the error value', () => { + expect(Raven.captureMessage).toHaveBeenCalledWith(req.statusText, { + extra: { + type: config.type, + url: config.url, + data: config.data, + status: req.status, + response: req.responseText.substring(0, 100), + error: req.statusText, + event, + }, + }); + }); + }); + + describe('if no req.responseText is provided', () => { + beforeEach(() => { + req.responseText = undefined; + + Raven.captureMessage.calls.reset(); + + RavenConfig.handleRavenErrors(event, req, config, err); + }); + + it('should use `Unknown response text` as the response', () => { + expect(Raven.captureMessage).toHaveBeenCalledWith(err, { + extra: { + type: config.type, + url: config.url, + data: config.data, + status: req.status, + response: 'Unknown response text', + error: err, + event, + }, + }); + }); + }); + }); + + describe('shouldSendSample', () => { + let randomNumber; + + beforeEach(() => { + RavenConfig.SAMPLE_RATE = 50; + + spyOn(Math, 'random').and.callFake(() => randomNumber); + }); + + it('should call Math.random', () => { + RavenConfig.shouldSendSample(); + + expect(Math.random).toHaveBeenCalled(); + }); + + it('should return true if the sample rate is greater than the random number * 100', () => { + randomNumber = 0.1; + + expect(RavenConfig.shouldSendSample()).toBe(true); + }); + + it('should return false if the sample rate is less than the random number * 100', () => { + randomNumber = 0.9; + + expect(RavenConfig.shouldSendSample()).toBe(false); + }); + + it('should return true if the sample rate is equal to the random number * 100', () => { + randomNumber = 0.5; + + expect(RavenConfig.shouldSendSample()).toBe(true); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index fdef0665d15..796fbbe0154 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3179,7 +3179,7 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" -json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -4573,6 +4573,12 @@ raphael@^2.2.7: dependencies: eve-raphael "0.5.0" +raven-js@^3.14.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.14.0.tgz#94dda81d975fdc4a42f193db437cf70021d654e0" + dependencies: + json-stringify-safe "^5.0.1" + raw-body@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96" |