summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/raven/index.js16
-rw-r--r--app/assets/javascripts/raven/raven_config.js100
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/models/application_setting.rb4
-rw-r--r--app/views/admin/application_settings/_form.html.haml15
-rw-r--r--app/views/layouts/_head.html.haml5
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/devise.html.haml1
-rw-r--r--app/views/layouts/devise_empty.html.haml1
-rw-r--r--config/application.rb3
-rw-r--r--config/webpack.config.js1
-rw-r--r--db/migrate/20170428123910_add_clientside_sentry_to_application_settings.rb12
-rw-r--r--db/schema.rb4
-rw-r--r--lib/api/settings.rb5
-rw-r--r--lib/api/v3/settings.rb6
-rw-r--r--lib/gitlab/gon_helper.rb3
-rw-r--r--package.json1
-rw-r--r--spec/features/raven_js_spec.rb23
-rw-r--r--spec/javascripts/raven/index_spec.js41
-rw-r--r--spec/javascripts/raven/raven_config_spec.js280
-rw-r--r--yarn.lock8
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"