summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/raven/index.js16
-rw-r--r--app/assets/javascripts/raven/raven_config.js47
-rw-r--r--app/helpers/sentry_helper.rb10
-rw-r--r--app/views/layouts/_head.html.haml3
-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/webpack.config.js5
-rw-r--r--lib/gitlab/gon_helper.rb5
-rw-r--r--package.json2
-rw-r--r--spec/features/raven_js_spec.rb24
-rw-r--r--spec/helpers/sentry_helper_spec.rb22
-rw-r--r--spec/javascripts/.eslintrc15
-rw-r--r--spec/javascripts/raven/index_spec.js41
-rw-r--r--spec/javascripts/raven/raven_config_spec.js192
-rw-r--r--yarn.lock12
16 files changed, 390 insertions, 8 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..bb9be7cb196
--- /dev/null
+++ b/app/assets/javascripts/raven/raven_config.js
@@ -0,0 +1,47 @@
+import Raven from 'raven-js';
+import $ from 'jquery';
+
+const RavenConfig = {
+ 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',
+ }).install();
+ },
+
+ setUser() {
+ Raven.setUserContext({
+ id: this.options.currentUserId,
+ });
+ },
+
+ bindRavenErrors() {
+ $(document).on('ajaxError.raven', this.handleRavenErrors);
+ },
+
+ handleRavenErrors(event, req, config, err) {
+ const error = err || req.statusText;
+
+ Raven.captureMessage(error, {
+ extra: {
+ type: config.type,
+ url: config.url,
+ data: config.data,
+ status: req.status,
+ response: req.responseText.substring(0, 100),
+ error,
+ event,
+ },
+ });
+ },
+};
+
+export default RavenConfig;
diff --git a/app/helpers/sentry_helper.rb b/app/helpers/sentry_helper.rb
index 3d255df66a0..4b07f71bcea 100644
--- a/app/helpers/sentry_helper.rb
+++ b/app/helpers/sentry_helper.rb
@@ -6,4 +6,14 @@ module SentryHelper
def sentry_context
Gitlab::Sentry.context(current_user)
end
+
+ def sentry_dsn_public
+ sentry_dsn = ApplicationSetting.current.sentry_dsn
+
+ return unless sentry_dsn
+
+ uri = URI.parse(sentry_dsn)
+ uri.password = nil
+ uri.to_s
+ end
end
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 19473b6ab27..8aef5cbdc04 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -28,9 +28,12 @@
= 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 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/webpack.config.js b/config/webpack.config.js
index ffb16190093..f034a8ae27d 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -50,8 +50,9 @@ var config = {
u2f: ['vendor/u2f'],
users: './users/users_bundle.js',
vue_pipelines: './vue_pipelines_index/index.js',
+ raven: './raven/index.js',
issue_show: './issue_show/index.js',
- group: './group.js',
+ group: './group.js'
},
output: {
@@ -67,7 +68,7 @@ var config = {
{
test: /\.js$/,
exclude: /(node_modules|vendor\/assets)/,
- loader: 'babel-loader',
+ loader: 'babel-loader?plugins=rewire',
},
{
test: /\.vue$/,
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 5ab84266b7d..4de504e9bf9 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -1,3 +1,5 @@
+include SentryHelper
+
module Gitlab
module GonHelper
def add_gon_variables
@@ -10,6 +12,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 = sentry_dsn_public if 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 a17399ddb8f..cf66d7ec58e 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
"mousetrap": "^1.4.6",
"pikaday": "^1.5.1",
"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",
@@ -54,6 +55,7 @@
},
"devDependencies": {
"babel-plugin-istanbul": "^4.0.0",
+ "babel-plugin-rewire": "^1.1.0",
"eslint": "^3.10.1",
"eslint-config-airbnb-base": "^10.0.1",
"eslint-import-resolver-webpack": "^0.8.1",
diff --git a/spec/features/raven_js_spec.rb b/spec/features/raven_js_spec.rb
new file mode 100644
index 00000000000..74df52d80a7
--- /dev/null
+++ b/spec/features/raven_js_spec.rb
@@ -0,0 +1,24 @@
+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
+ allow_any_instance_of(SentryHelper).to receive_messages(sentry_dsn_public: 'https://key@domain.com/id',
+ 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/helpers/sentry_helper_spec.rb b/spec/helpers/sentry_helper_spec.rb
new file mode 100644
index 00000000000..ff218235cd1
--- /dev/null
+++ b/spec/helpers/sentry_helper_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe SentryHelper do
+ describe '#sentry_dsn_public' do
+ it 'returns nil if no sentry_dsn is set' do
+ mock_sentry_dsn(nil)
+
+ expect(helper.sentry_dsn_public).to eq nil
+ end
+
+ it 'returns the uri string with no password if sentry_dsn is set' do
+ mock_sentry_dsn('https://test:dsn@host/path')
+
+ expect(helper.sentry_dsn_public).to eq 'https://test@host/path'
+ end
+ end
+
+ def mock_sentry_dsn(value)
+ allow_message_expectations_on_nil
+ allow(ApplicationSetting.current).to receive(:sentry_dsn).and_return(value)
+ end
+end
diff --git a/spec/javascripts/.eslintrc b/spec/javascripts/.eslintrc
index 3d922021978..a3a8bb050c9 100644
--- a/spec/javascripts/.eslintrc
+++ b/spec/javascripts/.eslintrc
@@ -27,6 +27,19 @@
"jasmine/no-suite-dupes": [1, "branch"],
"jasmine/no-spec-dupes": [1, "branch"],
"no-console": 0,
- "prefer-arrow-callback": 0
+ "prefer-arrow-callback": 0,
+ "no-underscore-dangle": [
+ 2,
+ {
+ "allow": [
+ "__GetDependency__",
+ "__Rewire__",
+ "__ResetDependency__",
+ "__get__",
+ "__set__",
+ "__RewireAPI__"
+ ]
+ }
+ ]
}
}
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..b8bb558d22e
--- /dev/null
+++ b/spec/javascripts/raven/raven_config_spec.js
@@ -0,0 +1,192 @@
+import Raven from 'raven-js';
+import RavenConfig, { __RewireAPI__ as RavenConfigRewire } from '~/raven/raven_config';
+
+describe('RavenConfig', () => {
+ 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;
+
+ beforeEach(() => {
+ options = {
+ sentryDsn: '//sentryDsn',
+ whitelistUrls: ['//gitlabUrl'],
+ isProduction: true,
+ };
+
+ raven = jasmine.createSpyObj('raven', ['install']);
+
+ spyOn(Raven, 'config').and.returnValue(raven);
+
+ RavenConfig.configure.call({
+ options,
+ });
+ });
+
+ it('should call Raven.config', () => {
+ expect(Raven.config).toHaveBeenCalledWith(options.sentryDsn, {
+ whitelistUrls: options.whitelistUrls,
+ environment: 'production',
+ });
+ });
+
+ it('should call Raven.install', () => {
+ expect(raven.install).toHaveBeenCalled();
+ });
+
+ it('should set .environment to development if isProduction is false', () => {
+ options.isProduction = false;
+
+ RavenConfig.configure.call({
+ options,
+ });
+
+ expect(Raven.config).toHaveBeenCalledWith(options.sentryDsn, {
+ whitelistUrls: options.whitelistUrls,
+ environment: 'development',
+ });
+ });
+ });
+
+ 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);
+
+ RavenConfigRewire.__set__('$', $);
+
+ RavenConfig.bindRavenErrors();
+ });
+
+ it('should query for document using jquery', () => {
+ expect($).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,
+ },
+ });
+ });
+ });
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index e16cd9c3673..e41f737c88c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -425,6 +425,10 @@ babel-plugin-istanbul@^4.0.0:
istanbul-lib-instrument "^1.4.2"
test-exclude "^4.0.0"
+babel-plugin-rewire@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.1.0.tgz#a6b966d9d8c06c03d95dcda2eec4e2521519549b"
+
babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
@@ -3157,7 +3161,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"
@@ -4530,6 +4534,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"