diff --git a/src/sentry/static/sentry/app/bootstrap.jsx b/src/sentry/static/sentry/app/bootstrap.jsx
new file mode 100644
index 000000000000..3412d8d3a33f
--- /dev/null
+++ b/src/sentry/static/sentry/app/bootstrap.jsx
@@ -0,0 +1,151 @@
+import 'bootstrap/js/alert';
+import 'bootstrap/js/tab';
+import 'bootstrap/js/dropdown';
+
+import 'app/utils/statics-setup';
+import 'app/utils/emotion-setup';
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Reflux from 'reflux';
+import * as Router from 'react-router';
+import * as Sentry from '@sentry/browser';
+import {ExtraErrorData, Tracing} from '@sentry/integrations';
+import createReactClass from 'create-react-class';
+import jQuery from 'jquery';
+import moment from 'moment';
+
+import {metric} from 'app/utils/analytics';
+import ConfigStore from 'app/stores/configStore';
+import Main from 'app/main';
+import ajaxCsrfSetup from 'app/utils/ajaxCsrfSetup';
+import plugins from 'app/plugins';
+
+// SDK INIT --------------------------------------------------------
+Sentry.init({
+ ...window.__SENTRY__OPTIONS,
+ integrations: [
+ new ExtraErrorData({
+ // 6 is arbitrary, seems like a nice number
+ depth: 6,
+ }),
+ new Tracing({
+ tracingOrigins: ['localhost', 'sentry.io', /^\//],
+ autoStartOnDomReady: false,
+ }),
+ ],
+});
+
+Sentry.configureScope(scope => {
+ if (window.__SENTRY__USER) {
+ scope.setUser(window.__SENTRY__USER);
+ }
+ if (window.__SENTRY__VERSION) {
+ scope.setTag('sentry_version', window.__SENTRY__VERSION);
+ }
+});
+
+// -----------------------------------------------------------------
+
+// Used for operational metrics to determine that the application js
+// bundle was loaded by browser.
+metric.mark('sentry-app-init');
+
+// setup jquery for CSRF tokens
+jQuery.ajaxSetup({
+ //jQuery won't allow using the ajaxCsrfSetup function directly
+ beforeSend: ajaxCsrfSetup,
+});
+
+// App setup
+if (window.__initialData) {
+ ConfigStore.loadInitialData(window.__initialData);
+}
+
+// these get exported to a global variable, which is important as its the only
+// way we can call into scoped objects
+
+const render = Component => {
+ const rootEl = document.getElementById('blk_router');
+
+ try {
+ ReactDOM.render(, rootEl);
+ } catch (err) {
+ // eslint-disable-next-line no-console
+ console.error(
+ new Error(
+ 'An unencoded "%" has appeared, it is super effective! (See https://github.com/ReactTraining/history/issues/505)'
+ )
+ );
+ if (err.message === 'URI malformed') {
+ window.location.assign(window.location.pathname);
+ }
+ }
+};
+
+// The password strength component is very heavyweight as it includes the
+// zxcvbn, a relatively byte-heavy password strength estimation library. Load
+// it on demand.
+async function loadPasswordStrength(callback) {
+ const module = await import(/* webpackChunkName: "passwordStrength" */ 'app/components/passwordStrength');
+ callback(module);
+}
+
+const globals = {
+ // This is the primary entrypoint for rendering the sentry app.
+ SentryRenderApp: () => render(Main),
+
+ // The following globals are used in sentry-plugins webpack externals
+ // configuration.
+ PropTypes,
+ React,
+ Reflux,
+ Router,
+ Sentry,
+ moment,
+ ReactDOM: {
+ findDOMNode: ReactDOM.findDOMNode,
+ render: ReactDOM.render,
+ },
+
+ // jQuery is still exported to the window as some bootsrap functionality
+ // makes use of it.
+ $: jQuery,
+ jQuery,
+
+ // django templates make use of these globals
+ createReactClass,
+};
+
+// The SentryApp global contains exported app modules for use in javascript
+// modules that are not compiled with the sentry bundle.
+globals.SentryApp = {
+ // The following components are used in sentry-plugins.
+ Form: require('app/components/forms/form').default,
+ FormState: require('app/components/forms/index').FormState,
+ LoadingIndicator: require('app/components/loadingIndicator').default,
+ plugins: {
+ add: plugins.add,
+ addContext: plugins.addContext,
+ BasePlugin: plugins.BasePlugin,
+ DefaultIssuePlugin: plugins.DefaultIssuePlugin,
+ },
+
+ // The following components are used in legacy django HTML views
+ passwordStrength: {load: loadPasswordStrength},
+ U2fSign: require('app/components/u2f/u2fsign').default,
+ ConfigStore: require('app/stores/configStore').default,
+ Alerts: require('app/components/alerts').default,
+ Indicators: require('app/components/indicators').default,
+ ProjectSelector: require('app/components/projectHeader/projectSelector').default,
+ Sidebar: require('app/components/sidebar').default,
+ SetupWizard: require('app/components/setupWizard').default,
+ OrganizationsLoader: require('app/components/organizations/organizationsLoader')
+ .default,
+};
+
+// Make globals available on the window object
+Object.keys(globals).forEach(name => (window[name] = globals[name]));
+
+export default globals;
diff --git a/src/sentry/static/sentry/app/index.js b/src/sentry/static/sentry/app/index.js
index ce538c18ee45..4780e2b7cbe4 100644
--- a/src/sentry/static/sentry/app/index.js
+++ b/src/sentry/static/sentry/app/index.js
@@ -1,153 +1,15 @@
import '@babel/polyfill';
-import 'bootstrap/js/alert';
-import 'bootstrap/js/tab';
-import 'bootstrap/js/dropdown';
-import 'app/utils/statics-setup';
-import 'app/utils/emotion-setup';
-
-import PropTypes from 'prop-types';
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Reflux from 'reflux';
-import * as Router from 'react-router';
-import * as Sentry from '@sentry/browser';
-import {ExtraErrorData, Tracing} from '@sentry/integrations';
-import createReactClass from 'create-react-class';
-import jQuery from 'jquery';
-import moment from 'moment';
-
-import {metric} from 'app/utils/analytics';
-import ConfigStore from 'app/stores/configStore';
-import Main from 'app/main';
-import ajaxCsrfSetup from 'app/utils/ajaxCsrfSetup';
-import plugins from 'app/plugins';
-
-// SDK INIT --------------------------------------------------------
-// window.__SENTRY__OPTIONS will be emmited by sdk-config.html before loading this script
-Sentry.init({
- ...window.__SENTRY__OPTIONS,
- integrations: [
- new ExtraErrorData({
- // 6 is arbitrary, seems like a nice number
- depth: 6,
- }),
- new Tracing({
- tracingOrigins: ['localhost', 'sentry.io', /^\//],
- autoStartOnDomReady: false,
- }),
- ],
-});
-
-Sentry.configureScope(scope => {
- if (window.__SENTRY__USER) {
- scope.setUser(window.__SENTRY__USER);
- }
- if (window.__SENTRY__VERSION) {
- scope.setTag('sentry_version', window.__SENTRY__VERSION);
- }
-});
-
-// -----------------------------------------------------------------
-
-// Used for operational metrics to determine that the application js
-// bundle was loaded by browser.
-metric.mark('sentry-app-init');
-
-// setup jquery for CSRF tokens
-jQuery.ajaxSetup({
- //jQuery won't allow using the ajaxCsrfSetup function directly
- beforeSend: ajaxCsrfSetup,
-});
-
-// App setup
+// Initial data hydration. The __initialData will be set by the django backend
+// serving sentry.
if (window.__initialData) {
- ConfigStore.loadInitialData(window.__initialData, window.__languageCode);
-}
-
-// these get exported to a global variable, which is important as its the only
-// way we can call into scoped objects
+ const {distPrefix, csrfCookieName, sentryConfig, userIdentity} = window.__initialData;
-const render = Component => {
- const rootEl = document.getElementById('blk_router');
-
- try {
- ReactDOM.render(, rootEl);
- } catch (err) {
- // eslint-disable-next-line no-console
- console.error(
- new Error(
- 'An unencoded "%" has appeared, it is super effective! (See https://github.com/ReactTraining/history/issues/505)'
- )
- );
- if (err.message === 'URI malformed') {
- window.location.assign(window.location.pathname);
- }
- }
-};
-
-// The password strength component is very heavyweight as it includes the
-// zxcvbn, a relatively byte-heavy password strength estimation library. Load
-// it on demand.
-async function loadPasswordStrength(callback) {
- const module = await import(/* webpackChunkName: "passwordStrength" */ 'app/components/passwordStrength');
- callback(module);
+ window.csrfCookieName = csrfCookieName;
+ window.__sentryGlobalStaticPrefix = distPrefix;
+ window.__SENTRY__OPTIONS = sentryConfig;
+ window.__SENTRY__USER = userIdentity;
}
-const globals = {
- // This is the primary entrypoint for rendering the sentry app.
- SentryRenderApp: () => render(Main),
-
- // The following globals are used in sentry-plugins webpack externals
- // configuration.
- PropTypes,
- React,
- Reflux,
- Router,
- Sentry,
- moment,
- ReactDOM: {
- findDOMNode: ReactDOM.findDOMNode,
- render: ReactDOM.render,
- },
-
- // jQuery is still exported to the window as some bootsrap functionality
- // makes use of it.
- $: jQuery,
- jQuery,
-
- // django templates make use of these globals
- createReactClass,
-};
-
-// The SentryApp global contains exported app modules for use in javascript
-// modules that are not compiled with the sentry bundle.
-globals.SentryApp = {
- // The following components are used in sentry-plugins.
- Form: require('app/components/forms/form').default,
- FormState: require('app/components/forms/index').FormState,
- LoadingIndicator: require('app/components/loadingIndicator').default,
- plugins: {
- add: plugins.add,
- addContext: plugins.addContext,
- BasePlugin: plugins.BasePlugin,
- DefaultIssuePlugin: plugins.DefaultIssuePlugin,
- },
-
- // The following components are used in legacy django HTML views
- passwordStrength: {load: loadPasswordStrength},
- U2fSign: require('app/components/u2f/u2fsign').default,
- ConfigStore: require('app/stores/configStore').default,
- Alerts: require('app/components/alerts').default,
- Indicators: require('app/components/indicators').default,
- ProjectSelector: require('app/components/projectHeader/projectSelector').default,
- Sidebar: require('app/components/sidebar').default,
- SetupWizard: require('app/components/setupWizard').default,
- OrganizationsLoader: require('app/components/organizations/organizationsLoader')
- .default,
-};
-
-// Make globals available on the window object
-Object.keys(globals).forEach(name => (window[name] = globals[name]));
-
-export default globals;
+// Once data hydration is done we can initialize the app
+require('app/bootstrap');
diff --git a/src/sentry/static/sentry/app/stores/configStore.jsx b/src/sentry/static/sentry/app/stores/configStore.jsx
index 3f615bd0e75c..fad536777f86 100644
--- a/src/sentry/static/sentry/app/stores/configStore.jsx
+++ b/src/sentry/static/sentry/app/stores/configStore.jsx
@@ -23,10 +23,13 @@ const ConfigStore = Reflux.createStore({
return this.config;
},
- loadInitialData(config, languageOverride = null) {
+ loadInitialData(config) {
config.features = new Set(config.features || []);
this.config = config;
+ // Language code is passed from django
+ let languageCode = config.languageCode;
+
// TODO(dcramer): abstract this out of ConfigStore
if (config.user) {
config.user.permissions = new Set(config.user.permissions);
@@ -43,15 +46,13 @@ const ConfigStore = Reflux.createStore({
// e.g. unencoded "%"
}
- // Priority:
- // "?lang=en" --> user configuration options --> django request.LANGUAGE_CODE --> "en"
- setLocale(
- queryString.lang || config.user.options.language || languageOverride || 'en'
- );
- } else if (languageOverride) {
- setLocale(languageOverride);
+ languageCode = queryString.lang || config.user.options.language || languageCode;
}
+ // Priority:
+ // "?lang=en" --> user configuration options --> django request.LANGUAGE_CODE --> "en"
+ setLocale(languageCode || 'en');
+
this.trigger(config);
},
});
diff --git a/src/sentry/static/sentry/app/views/admin/adminUserEdit.jsx b/src/sentry/static/sentry/app/views/admin/adminUserEdit.jsx
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/src/sentry/templates/sentry/layout.html b/src/sentry/templates/sentry/layout.html
index 8ebe3d98667b..9a1ce4c47881 100644
--- a/src/sentry/templates/sentry/layout.html
+++ b/src/sentry/templates/sentry/layout.html
@@ -34,14 +34,10 @@
{% block title %}Sentry{% endblock %}
{% block scripts %}
- {% include "sentry/includes/sdk-config.html" %}
{% locale_js_include %}
diff --git a/src/sentry/templatetags/sentry_react.py b/src/sentry/templatetags/sentry_react.py
index 2cc7c3351d2f..72f737e42ab7 100644
--- a/src/sentry/templatetags/sentry_react.py
+++ b/src/sentry/templatetags/sentry_react.py
@@ -78,10 +78,25 @@ def get_react_config(context):
messages = get_messages(request)
session = getattr(request, 'session', None)
is_superuser = is_active_superuser(request)
+ language_code = getattr(request, 'LANGUAGE_CODE', 'en')
else:
user = None
messages = []
is_superuser = False
+ language_code = 'en'
+
+ # User identity is used by the sentry SDK
+ if request and user:
+ user_identity = {'ip_address': request.META['REMOTE_ADDR']}
+ if user and user.is_authenticated():
+ user_identity.update({
+ 'email': user.email,
+ 'id': user.id,
+ })
+ if user.name:
+ user_identity['name'] = user.name
+ else:
+ user_identity = {}
enabled_features = []
if features.has('organizations:create', actor=user):
@@ -103,6 +118,7 @@ def get_react_config(context):
'version': version_info,
'features': enabled_features,
'mediaUrl': get_asset_url('sentry', ''),
+ 'distPrefix': get_asset_url('sentry', 'dist/'),
'needsUpgrade': needs_upgrade,
'dsn': get_public_dsn(),
'statuspage': _get_statuspage(),
@@ -119,6 +135,14 @@ def get_react_config(context):
# It should only be used on a fresh browser nav to a path where an
# organization is not in context
'lastOrganization': session['activeorg'] if session and 'activeorg' in session else None,
+ 'languageCode': language_code,
+ 'userIdentity': user_identity,
+ 'csrfCookieName': settings.CSRF_COOKIE_NAME,
+ 'sentryConfig': {
+ 'dsn': get_public_dsn(),
+ 'release': version_info['build'],
+ 'whitelistUrls': list(settings.ALLOWED_HOSTS),
+ },
}
if user and user.is_authenticated():
context.update({
diff --git a/src/sentry/web/helpers.py b/src/sentry/web/helpers.py
index 8bb807f418d0..f77bbbd3844b 100644
--- a/src/sentry/web/helpers.py
+++ b/src/sentry/web/helpers.py
@@ -27,7 +27,6 @@ def get_default_context(request, existing_context=None, team=None):
from sentry.plugins import plugins
context = {
- 'CSRF_COOKIE_NAME': settings.CSRF_COOKIE_NAME,
'URL_PREFIX': options.get('system.url-prefix'),
'SINGLE_ORGANIZATION': settings.SENTRY_SINGLE_ORGANIZATION,
'PLUGINS': plugins,