+ Users and logs provide clues. Sentry provides answers.
+
+
+What's Sentry?
+--------------
-Sentry is a Server
-------------------
+Sentry fundamentally is a service that helps you monitor and fix crashes in realtime.
+The server is in Python, but it contains a full API for sending events from any
+language, in any application.
-The Sentry package fundamentally is just a simple server and web UI. It will
-handle authenticating SDKs (such as `Raven `_),
-all of the logic behind storage and aggregation, and alerting team members.
+.. raw:: html
-That said, Sentry is not limited to Python. The server is in Python, but it contains
-a full API for sending events from any language, in any application.
+
+
+
+
+
Official Sentry SDKs
~~~~~~~~~~~~~~~~~~~~
@@ -28,12 +38,14 @@ Official Sentry SDKs
* `C# `_
* `Perl `_
* `Elixir `_
+* `Laravel `_
Resources
---------
* `Documentation `_
* `Community `_ (Bugs, feature requests, general questions)
+* `Contributing `_
* `Bug Tracker `_
* `Code `_
* `IRC `_ (irc.freenode.net, #sentry)
diff --git a/api-docs/generator.py b/api-docs/generator.py
index 6aaf562939b03d..4a96075938f953 100644
--- a/api-docs/generator.py
+++ b/api-docs/generator.py
@@ -167,7 +167,7 @@ def cli(output_path):
projects = []
for project_name in 'Pump Station', 'Prime Mover':
report('project', 'Creating project "%s"' % project_name)
- project = utils.create_project(project_name, team=team, org=org)
+ project = utils.create_project(project_name, teams=[team], org=org)
release = utils.create_release(project=project, user=user)
report('event', 'Creating event for "%s"' % project_name)
diff --git a/bin/dump-command-help b/bin/dump-command-help
index 839e6b35cd6978..47f6e3983ffad8 100755
--- a/bin/dump-command-help
+++ b/bin/dump-command-help
@@ -71,7 +71,13 @@ def write_page(out, data):
f.write('%s\n' % line.encode('utf-8'))
+class NoHelp(Exception):
+ pass
+
+
def dump_command(out, cmd, path):
+ if cmd.help is None:
+ raise NoHelp
data = {
'path': path,
'help': cmd.help.replace('\b', ''),
@@ -99,8 +105,12 @@ def dump_command(out, cmd, path):
if isinstance(cmd, click.Group):
for child_name, child_cmd in six.iteritems(cmd.commands):
- dump_command(out, child_cmd, path + [child_name])
- data['subcommands'].append(child_name)
+ try:
+ dump_command(out, child_cmd, path + [child_name])
+ except NoHelp:
+ pass
+ else:
+ data['subcommands'].append(child_name)
write_page(out, data)
diff --git a/bin/lint b/bin/lint
index 0a9955cc54a506..ac95274e7bee51 100755
--- a/bin/lint
+++ b/bin/lint
@@ -1,29 +1,38 @@
#!/usr/bin/env python
from __future__ import absolute_import
+import click
import os
import sys
# This is to avoid needing to have the `sentry` package explicitly installed.
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, 'src'))
-from sentry.lint.engine import run
-offset = 1
-js = True
-py = True
+@click.command()
+@click.argument('files', nargs=-1)
+@click.option('--js', default=None, is_flag=True)
+@click.option('--python', default=None, is_flag=True)
+@click.option('--format', default=False, is_flag=True)
+@click.option('--parseable', default=False, is_flag=True)
+def run(files, js, python, format, parseable):
+ from sentry.lint import engine
-# Allow passing a flag for --js or --python only
-if len(sys.argv) > 1:
- if sys.argv[1] == '--js':
- offset = 2
- py = False
- elif sys.argv[1] == '--python':
- offset = 2
+ if js and not python:
+ python = False
+ elif python and not js:
js = False
+ else:
+ js = True
+ python = True
-file_list = sys.argv[offset:]
-if not file_list:
- file_list = None
+ if not files:
+ files = None
-sys.exit(run(file_list, js=js, py=py, format=False))
+ results = engine.run(files, js=js, py=python, format=format, parseable=parseable)
+ if results:
+ raise click.Abort
+
+
+if __name__ == '__main__':
+ run()
diff --git a/bin/load-mocks b/bin/load-mocks
index 6c5be7ea162a10..77b095c76c465e 100755
--- a/bin/load-mocks
+++ b/bin/load-mocks
@@ -171,7 +171,7 @@ def create_sample_time_series(event, release=None):
environment = Environment.get_or_create(
project=project,
- name=event.get_tag('environment') or ''
+ name=Environment.get_name_or_default(event.get_tag('environment')),
)
if release:
@@ -194,7 +194,7 @@ def create_sample_time_series(event, release=None):
tsdb.incr_multi((
(tsdb.models.project, project.id),
(tsdb.models.group, group.id),
- ), now, count)
+ ), now, count, environment_id=environment.id)
tsdb.incr_multi((
(tsdb.models.organization_total_received, project.organization_id),
(tsdb.models.project_total_forwarded, project.id),
@@ -242,7 +242,7 @@ def create_sample_time_series(event, release=None):
tsdb.incr_multi((
(tsdb.models.project, group.project.id),
(tsdb.models.group, group.id),
- ), now, count)
+ ), now, count, environment_id=environment.id)
tsdb.incr_multi((
(tsdb.models.organization_total_received, project.organization_id),
(tsdb.models.project_total_received, project.id),
diff --git a/bin/merge-catalogs b/bin/merge-catalogs
index ce1691fe582bf7..223c4e4adee17b 100755
--- a/bin/merge-catalogs
+++ b/bin/merge-catalogs
@@ -29,9 +29,9 @@ def cli(locale):
return
with open(catalog_file) as f:
- catalog = read_po(f)
+ catalog = read_po(f, locale)
with open(frontend_file) as f:
- frontend = read_po(f)
+ frontend = read_po(f, locale)
for frontend_msg in frontend:
if frontend_msg.id == '':
diff --git a/bin/scan b/bin/scan
new file mode 100755
index 00000000000000..2cd94728775659
--- /dev/null
+++ b/bin/scan
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+ignored=(
+ # djangorestframework, installed 2.4.8, affected <3.1.1, id 25804
+ # Description: Escape tab switching cookie name in browsable API.
+ # Reason: We do not use the browsable API
+ 25804
+ # django, installed 1.6.11, affected <1.7.11, id 25714
+ # Description: The get_format function in utils/formats.py in Django before 1.7.x before 1.7.11, 1.8.x before 1.8.7, and 1.9.x before 1.9rc2 might allow remote attackers to obtain sensitive application secrets via a settings key in place of a date/time format setting, as demonstrated by SECRET_KEY.
+ # Reason: We don't leverage this feature.
+ 25714
+ # django, installed 1.6.11, affected <1.7.6, id 25715
+ # Description: Cross-site scripting (XSS) vulnerability in the contents function in admin/helpers.py in Django before 1.7.6 and 1.8 before 1.8b2 allows remote attackers to inject arbitrary web script or HTML via a model attribute in ModelAdmin.readonly_fields, as demonstrated by a @property.
+ # Reason: We don't leverage this feature.
+ 25715
+ # django, installed 1.6.11, affected <1.8.10, id 33073
+ # Description: The utils.http.is_safe_url function in Django before 1.8.10 and 1.9.x before 1.9.3 allows remote attackers to redirect users to arbitrary web sites and conduct phishing attacks or possibly conduct cross-site scripting (XSS) attacks via a URL containing basic authentication, as demonstrated by http://mysite.example.com\@attacker.com.
+ # Reason: We have already patched around this and use our own `is_safe_url` function pulled from Django 1.10.
+ 33073
+ # django, installed 1.6.11, affected <1.8.10, id 33074
+ # Description: The password hasher in contrib/auth/hashers.py in Django before 1.8.10 and 1.9.x before 1.9.3 allows remote attackers to enumerate users via a timing attack involving login requests
+ # Reason: We don't leverage this feature, nor would a timing attack be viable due to rate limiting.
+ 33074
+ # django, installed 1.6.11, affected <1.8.15, id 25718
+ # Description: The cookie parsing code in Django before 1.8.15 and 1.9.x before 1.9.10, when used on a site with Google Analytics, allows remote attackers to bypass an intended CSRF protection mechanism by setting arbitrary cookies.
+ # Reason: We have backported the patch.
+ 25718
+ # django, installed 1.6.11, affected >=1.5,<1.7, id 25725
+ # Description: The session backends in Django before 1.4.21, 1.5.x through 1.6.x, 1.7.x before 1.7.9, and 1.8.x before 1.8.3 allows remote attackers to cause a denial of service (session store consumption) via multiple requests with unique session keys.
+ # Reason: This does not apply to us with the cookie based session backend.
+ 25725
+)
+
+args="--full-report"
+for i in ${ignored[@]}; do
+ args="$args --ignore=${i}"
+done
+
+exec safety check ${args}
diff --git a/bin/update-zeus-job b/bin/update-zeus-job
deleted file mode 100755
index df39cb65741687..00000000000000
--- a/bin/update-zeus-job
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash -eu
-
-# TODO(dcramer): pass URL
-curl $ZEUS_HOOK_BASE/builds/$TRAVIS_BUILD_NUMBER \
- -X POST \
- -H 'Content-Type: application/json' \
- -d "{\"ref\": \"$TRAVIS_COMMIT\", \"url\": \"https://travis-ci.org/${TRAVIS_REPO_SLUG}/builds/${TRAVIS_BUILD_ID}\"}"
-
-curl $ZEUS_HOOK_BASE/builds/$TRAVIS_BUILD_NUMBER/jobs/$TRAVIS_JOB_NUMBER \
- -X POST \
- -H 'Content-Type: application/json' \
- -d "{\"label\": \"${TEST_SUITE}\", \"status\": \"$1\", \"result\": \"$2\", \"url\": \"https://travis-ci.org/${TRAVIS_REPO_SLUG}/jobs/${TRAVIS_JOB_ID}\", \"allow_failure\": ${TRAVIS_ALLOW_FAILURE}}"
diff --git a/bin/upload-zeus-artifact b/bin/upload-zeus-artifact
deleted file mode 100755
index 2d7331f943fc0a..00000000000000
--- a/bin/upload-zeus-artifact
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash -eu
-
-if [[ -e $1 ]]; then
- curl $ZEUS_HOOK_BASE/builds/$TRAVIS_BUILD_NUMBER/jobs/$TRAVIS_JOB_NUMBER/artifacts \
- -X POST \
- -F "file=@$1"
-else
- (>&2 echo "[WARN] File not found: $1")
-fi
diff --git a/codecov.yml b/codecov.yml
index 0deb635fd4cae3..d549a35f47086f 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -6,11 +6,20 @@ coverage:
default: false
python:
paths:
- - "src/sentry"
+ - "src/sentry/**/*.py"
target: 90%
+ javascript:
+ paths:
+ - "src/sentry/static/sentry/app"
+ target: 75%
ignore:
- src/sentry/lint/.*
- src/sentry/runner/.*
+ - src/sentry/debug/.*
- src/.*?/migrations/.*
- src/.*?/south_migrations/.*
+ - src/bitfield/.*
+ - src/debug_toolbar/.*
+ - src/social_auth/.*
+ - src/south/.*
comment: false
diff --git a/docs-ui/components/alertLink.stories.js b/docs-ui/components/alertLink.stories.js
new file mode 100644
index 00000000000000..77889a9d8e4146
--- /dev/null
+++ b/docs-ui/components/alertLink.stories.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import {storiesOf} from '@storybook/react';
+import {withInfo} from '@storybook/addon-info';
+
+import AlertLink from 'sentry-ui/alertLink';
+
+storiesOf('Links/AlertLink', module)
+ .add(
+ 'default',
+ withInfo('A way to loudly link between different parts of the application')(() => (
+
+ Check out the notifications settings panel
+
+ ))
+ )
+ .add(
+ 'with an icon',
+ withInfo('You can optionally pass an icon src')(() => (
+
+ Check out the notifications settings panel
+
+ ))
+ );
diff --git a/docs-ui/components/alertMessage.stories.js b/docs-ui/components/alertMessage.stories.js
index 04d41d93b93f92..da3efb65019df2 100644
--- a/docs-ui/components/alertMessage.stories.js
+++ b/docs-ui/components/alertMessage.stories.js
@@ -14,7 +14,7 @@ storiesOf('AlertMessage', module).add(
id: 'id',
message: 'Alert Message',
type: 'success',
- url: 'url'
+ url: 'url',
}}
/>
@@ -23,7 +23,7 @@ storiesOf('AlertMessage', module).add(
id: 'id',
message: 'Alert Message',
type: 'error',
- url: 'url'
+ url: 'url',
}}
/>
diff --git a/docs-ui/components/autoComplete.stories.js b/docs-ui/components/autoComplete.stories.js
new file mode 100644
index 00000000000000..e52e7db5bd0c6a
--- /dev/null
+++ b/docs-ui/components/autoComplete.stories.js
@@ -0,0 +1,83 @@
+import React from 'react';
+import {storiesOf} from '@storybook/react';
+import {withInfo} from '@storybook/addon-info';
+// import {action} from '@storybook/addon-actions';
+
+import AutoComplete from 'sentry-ui/autoComplete';
+
+const items = [
+ {
+ name: 'Apple',
+ },
+ {
+ name: 'Pineapple',
+ },
+ {
+ name: 'Orange',
+ },
+];
+
+storiesOf('AutoComplete', module).add(
+ 'default',
+ withInfo('Description')(() => (
+ item.name}>
+ {({
+ getRootProps,
+ getInputProps,
+ getMenuProps,
+ getItemProps,
+ inputValue,
+ selectedItem,
+ highlightedIndex,
+ isOpen,
+ }) => {
+ return (
+
))
)
@@ -55,28 +39,25 @@ storiesOf('ComponentLayouts/FlowLayout', module)
- Very very long content Very very long content Very very long content Very very long content
+ Very very long content Very very long content Very very long content Very
+ very long content
-
- Important
-
+
Important
Without "truncate"
- Very very long content Very very long content Very very long content Very very long content
+ Very very long content Very very long content Very very long content Very
+ very long content
-
- Important
-
+
Important
-
))
);
diff --git a/docs-ui/components/form.stories.js b/docs-ui/components/form.stories.js
index 9d363300ca715f..e721c224b08ea9 100644
--- a/docs-ui/components/form.stories.js
+++ b/docs-ui/components/form.stories.js
@@ -1,17 +1,62 @@
import React from 'react';
+import PropTypes from 'prop-types';
import {storiesOf} from '@storybook/react';
import {withInfo} from '@storybook/addon-info';
import {action} from '@storybook/addon-actions';
-import {Form, TextField, PasswordField, BooleanField} from 'sentry-ui/forms';
+import {
+ Form as LegacyForm,
+ TextField as LegacyTextField,
+ PasswordField,
+ BooleanField,
+} from 'sentry-ui/forms';
+import RadioField from 'settings-ui/forms/radioField';
+import RadioGroup from 'settings-ui/forms/radioGroup';
+import Form from 'settings-ui/forms/form';
+import FormField from 'settings-ui/forms/formField';
+import TextField from 'settings-ui/forms/textField';
+
+class UndoButton extends React.Component {
+ static contextTypes = {
+ form: PropTypes.object,
+ };
+
+ handleClick = e => {
+ e.preventDefault();
+ this.context.form.undo();
+ };
+
+ render() {
+ return (
+
+ );
+ }
+}
// eslint-disable-next-line
storiesOf('Forms/Form', module)
- .add('empty', withInfo('Empty form')(() => ))
+ .add('empty', withInfo('Empty form')(() => ))
.add(
'with Cancel',
withInfo('Adds a "Cancel" button when `onCancel` is defined')(() => (
-
+
+ ))
+ )
+ .add(
+ 'save on blur and undo',
+ withInfo('Saves on blur and has undo')(() => (
+
+
+
+
+
))
);
@@ -19,28 +64,71 @@ storiesOf('Forms/Fields', module)
.add(
'TextField',
withInfo('Simple text input')(() => (
-
))
);
diff --git a/docs-ui/components/multipleCheckbox.stories.js b/docs-ui/components/multipleCheckbox.stories.js
new file mode 100644
index 00000000000000..772cfe521fa46f
--- /dev/null
+++ b/docs-ui/components/multipleCheckbox.stories.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import {storiesOf} from '@storybook/react';
+import {withInfo} from '@storybook/addon-info';
+import {action} from '@storybook/addon-actions';
+
+import MultipleCheckbox from 'application-root/views/settings/components/forms/controls/multipleCheckbox';
+
+storiesOf('Forms/Fields', module).add(
+ 'MultipleCheckbox',
+ withInfo('Multiple Checkbox Control (controlled only atm)')(() => (
+ {
+ action('MultipleCheckbox change')(v, e);
+ }}
+ />
+ ))
+);
diff --git a/docs-ui/components/narrowLayout.stories.js b/docs-ui/components/narrowLayout.stories.js
index 25c091562ab992..7a801e4c772c4d 100644
--- a/docs-ui/components/narrowLayout.stories.js
+++ b/docs-ui/components/narrowLayout.stories.js
@@ -7,9 +7,5 @@ import NarrowLayout from 'sentry-ui/narrowLayout';
storiesOf('NarrowLayout', module).add(
'default',
- withInfo('A narrow layout')(() => (
-
- Narrow Layout
-
- ))
+ withInfo('A narrow layout')(() => Narrow Layout)
);
diff --git a/docs-ui/components/pills.stories.js b/docs-ui/components/pills.stories.js
index c8ffc547f3d221..39d80a8f8e6e8b 100644
--- a/docs-ui/components/pills.stories.js
+++ b/docs-ui/components/pills.stories.js
@@ -11,8 +11,12 @@ storiesOf('Pills', module).add(
withInfo('When you have key/value data but are tight on space.')(() => (
- thing
- thing
+
+ thing
+
+
+ thing
+ thing
))
diff --git a/docs-ui/components/scoreBar.stories.js b/docs-ui/components/scoreBar.stories.js
index 163de5f01ee7d1..edd1726c0afa3b 100644
--- a/docs-ui/components/scoreBar.stories.js
+++ b/docs-ui/components/scoreBar.stories.js
@@ -44,7 +44,7 @@ stories
color('Low', 'yellow'),
color('Med', 'lime'),
color('High', 'blue'),
- color('Higher', 'purple')
+ color('Higher', 'purple'),
]);
return (
diff --git a/docs-ui/components/splitLayout.stories.js b/docs-ui/components/splitLayout.stories.js
index c2873a2a98680d..9cfb7dd45f5bfe 100644
--- a/docs-ui/components/splitLayout.stories.js
+++ b/docs-ui/components/splitLayout.stories.js
@@ -9,12 +9,8 @@ storiesOf('ComponentLayouts/SplitLayout', module).add(
'default',
withInfo('Children elements have equal size')(() => (
-
- Split
-
-
- Layout
-
+
Split
+
Layout
))
);
diff --git a/docs-ui/components/spreadLayout.stories.js b/docs-ui/components/spreadLayout.stories.js
index 70373beafb2a46..6190e79384ca76 100644
--- a/docs-ui/components/spreadLayout.stories.js
+++ b/docs-ui/components/spreadLayout.stories.js
@@ -10,12 +10,8 @@ storiesOf('ComponentLayouts/SpreadLayout', module)
'default',
withInfo('Children elements get spread out (flexbox space-between)')(() => (
-
- Spread
-
-
- Layout
-
+
Spread
+
Layout
))
)
@@ -23,12 +19,8 @@ storiesOf('ComponentLayouts/SpreadLayout', module)
'no center',
withInfo('Children elements get spread out (flexbox space-between)')(() => (
-
- Spread
-
-
- Layout
-
+
Spread
+
Layout
))
);
diff --git a/docs-ui/components/stackedBarChart.stories.js b/docs-ui/components/stackedBarChart.stories.js
index 626d6bed6a449c..6a9f323dbbc85c 100644
--- a/docs-ui/components/stackedBarChart.stories.js
+++ b/docs-ui/components/stackedBarChart.stories.js
@@ -13,16 +13,16 @@ storiesOf('StackedBarChart', module).add(
series={[
{
data: [{x: 1461099600, y: 31734}, {x: 1461103200, y: 36790}],
- label: 'received'
+ label: 'received',
},
{
data: [{x: 1461099600, y: 2867}, {x: 1461103200, y: 2742}],
- label: 'rejected'
+ label: 'rejected',
},
{
data: [{x: 1461099600, y: 0}, {x: 1461103200, y: 0}],
- label: 'blacklisted'
- }
+ label: 'blacklisted',
+ },
]}
className="dashboard-barchart standard-barchart"
height="100%"
diff --git a/docs-ui/components/tag.stories.js b/docs-ui/components/tag.stories.js
new file mode 100644
index 00000000000000..65362d8d1ceec0
--- /dev/null
+++ b/docs-ui/components/tag.stories.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import {storiesOf} from '@storybook/react';
+import {withInfo} from '@storybook/addon-info';
+
+import Tag from 'settings-ui/tag';
+
+storiesOf('Tags', module)
+ .add(
+ 'default',
+ withInfo('A basic tag-like thing. If you pass no type, it will be gray')(() => (
+ Development
+ ))
+ )
+ .add(
+ 'warning',
+ withInfo(
+ 'A warning tag-like thing. Use this to signal that something is maybe not so great'
+ )(() => Development)
+ )
+ .add(
+ 'success',
+ withInfo('A happy tag-like thing. Use this to signal something good')(() => (
+ Development
+ ))
+ );
diff --git a/docs-ui/components/textCopyInput.stories.js b/docs-ui/components/textCopyInput.stories.js
new file mode 100644
index 00000000000000..2b8507234a8678
--- /dev/null
+++ b/docs-ui/components/textCopyInput.stories.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import {storiesOf} from '@storybook/react';
+import {withInfo} from '@storybook/addon-info';
+import {action} from '@storybook/addon-actions';
+
+import TextCopyInput from 'application-root/views/settings/components/forms/textCopyInput';
+
+storiesOf('TextCopyInput', module).add(
+ 'default',
+ withInfo('Description')(() => (
+ Value to be copied
+ ))
+);
diff --git a/docs-ui/components/textOverflow.stories.js b/docs-ui/components/textOverflow.stories.js
new file mode 100644
index 00000000000000..9fcc57cf6176e7
--- /dev/null
+++ b/docs-ui/components/textOverflow.stories.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import {storiesOf} from '@storybook/react';
+import {withInfo} from '@storybook/addon-info';
+
+import TextOverflow from 'sentry-ui/textOverflow';
+
+storiesOf('TextOverflow', module).add(
+ 'default',
+ withInfo(
+ 'Simple component that adds "text-overflow: ellipsis" and "overflow: hidden", still depends on container styles'
+ )(() => (
+
+ AReallyLongTextString
+
+ ))
+);
diff --git a/docs-ui/components/toolbar.stories.js b/docs-ui/components/toolbar.stories.js
index 57e2bb9de47c0e..48829d867dd5d7 100644
--- a/docs-ui/components/toolbar.stories.js
+++ b/docs-ui/components/toolbar.stories.js
@@ -14,7 +14,8 @@ storiesOf('Toolbar', module).add(
)(() => (
- LeftRight
+ Left
+ Right
))
diff --git a/docs-ui/components/tooltip.stories.js b/docs-ui/components/tooltip.stories.js
new file mode 100644
index 00000000000000..1c446a9f25da9f
--- /dev/null
+++ b/docs-ui/components/tooltip.stories.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import {storiesOf} from '@storybook/react';
+import {withInfo} from '@storybook/addon-info';
+
+import Tooltip from 'sentry-ui/tooltip';
+import Button from 'sentry-ui/buttons/button';
+
+storiesOf('Tooltip', module).add(
+ 'default',
+ withInfo('Description')(() => (
+
+
Test
+
+
+ Custom React Component
+
+
+
+
+
+ Native button
+
+
+
+ ))
+);
diff --git a/docs-ui/index.js b/docs-ui/index.js
index 6307076958d0eb..277e43fc76cad2 100644
--- a/docs-ui/index.js
+++ b/docs-ui/index.js
@@ -8,6 +8,6 @@ import ConfigStore from '../src/sentry/static/sentry/app/stores/configStore';
ConfigStore.loadInitialData({
gravatarBaseUrl: 'https://secure.gravatar.com',
version: {
- current: 'Storybook'
- }
+ current: 'Storybook',
+ },
});
diff --git a/docs/cli/cleanup/index.rst b/docs/cli/cleanup/index.rst
index 90f28282a36582..090e38f7b2a169 100644
--- a/docs/cli/cleanup/index.rst
+++ b/docs/cli/cleanup/index.rst
@@ -14,8 +14,15 @@ Options
- ``--days INTEGER``: Numbers of days to truncate on. [default: 30]
- ``--project TEXT``: Limit truncation to only entries from project.
-- ``--concurrency INTEGER``: The number of concurrent workers to run.
- [default: 1]
+- ``--concurrency INTEGER``: The total number of concurrent threads to run
+ across processes. [default: 1]
+- ``--max_procs INTEGER``: The maximum number of processes to fork off for
+ concurrency. [default: 8]
- ``-q, --silent``: Run quietly. No output on success.
+- ``-r, --router TEXT``: Database router
+- ``-t, --timed``: Send the duration of this command to internal metrics.
+- ``-l, --loglevel [DEBUG|INFO|WARNING|ERROR|CRITICAL|FATAL]``: Global
+ logging level. Use wisely.
+- ``--logformat [human|machine]``: Log line format.
- ``--help``: print this help page.
diff --git a/docs/cli/config/index.rst b/docs/cli/config/index.rst
index c00c4d660da4f2..5b4a8f23015cc5 100644
--- a/docs/cli/config/index.rst
+++ b/docs/cli/config/index.rst
@@ -14,9 +14,10 @@ Subcommands
.. toctree::
:maxdepth: 1
- delete
set
+ get
list
+ discover
generate-secret-key
- get
+ delete
diff --git a/docs/cli/config/set/index.rst b/docs/cli/config/set/index.rst
index 5b9906b12b6b9a..d64f0491b2d6b8 100644
--- a/docs/cli/config/set/index.rst
+++ b/docs/cli/config/set/index.rst
@@ -1,9 +1,10 @@
-`sentry config set OPTION VALUE`
---------------------------------
+`sentry config set KEY [VALUE]`
+-------------------------------
Set a configuration option to a new value.
Options
```````
+- ``--secret``: Hide prompt input when inputing secret data.
- ``--help``: print this help page.
diff --git a/docs/cli/devserver/index.rst b/docs/cli/devserver/index.rst
index 61e58ff4ff0afc..d6e660ea0b1925 100644
--- a/docs/cli/devserver/index.rst
+++ b/docs/cli/devserver/index.rst
@@ -10,6 +10,11 @@ Options
- ``--watchers / --no-watchers``: Watch static files and recompile on
changes.
- ``--workers / --no-workers``: Run asynchronous workers.
+- ``--browser-reload / --no-browser-reload``: Automatic browser refreshing
+ on webpack builds
+- ``--styleguide / --no-styleguide``: Start local styleguide web server on
+ port 9001
+- ``--environment TEXT``: The environment name.
- ``-l, --loglevel [DEBUG|INFO|WARNING|ERROR|CRITICAL|FATAL]``: Global
logging level. Use wisely.
- ``--logformat [human|machine]``: Log line format.
diff --git a/docs/cli/dsym/import-system-symbols/index.rst b/docs/cli/dsym/import-system-symbols/index.rst
deleted file mode 100644
index 91fa78b2ed4cd2..00000000000000
--- a/docs/cli/dsym/import-system-symbols/index.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-`sentry dsym import-system-symbols [BUNDLES]...`
-------------------------------------------------
-
-Imports system symbols from preprocessed zip files into Sentry.
-
-It takes a list of zip files as arguments that contain preprocessed
-system symbol information. These zip files contain JSON dumps. The
-actual zipped up dsym files cannot be used here, they need to be
-preprocessed.
-
-Options
-```````
-
-- ``--threads INTEGER``: The number of threads to use
-- ``--trim-symbols``: If enabled symbols are trimmed before storing. This
- reduces the database size but means that symbols are already trimmed on
- the way to the database.
-- ``--no-demangle``: If this is set to true symbols are never demangled.
- By default symbols are demangled if they are trimmed or demangled
- symbols are shorter than mangled ones. Enabling this option speeds up
- importing slightly.
-- ``--help``: print this help page.
diff --git a/docs/cli/dsym/index.rst b/docs/cli/dsym/index.rst
deleted file mode 100644
index de338824072bba..00000000000000
--- a/docs/cli/dsym/index.rst
+++ /dev/null
@@ -1,24 +0,0 @@
-`sentry dsym`
--------------
-
-Manage system symbols in Sentry.
-
-This allows you to import and manage globally shared system symbols in
-the Sentry installation. In particular this is useful for iOS where
-system symbols need to be ingested before stacktraces can be fully
-symbolized due to device optimizations.
-
-Options
-```````
-
-- ``--help``: print this help page.
-
-Subcommands
-```````````
-
-.. toctree::
- :maxdepth: 1
-
- sdks
- import-system-symbols
-
diff --git a/docs/cli/dsym/sdks/index.rst b/docs/cli/dsym/sdks/index.rst
deleted file mode 100644
index 4ced22487fcb29..00000000000000
--- a/docs/cli/dsym/sdks/index.rst
+++ /dev/null
@@ -1,17 +0,0 @@
-`sentry dsym sdks`
-------------------
-
-Print a list of all installed SDKs and a breakdown of the symbols
-contained within. This queries the system symbol database and reports
-all SDKs and versions that symbols exist for. The output is broken down
-by minor versions, builds and cpu architectures. For each of those a
-count of the stored bundles is returned. (A bundle in this case is a
-single binary)
-
-Options
-```````
-
-- ``--sdk TEXT``: Only include the given SDK instead of all.
-- ``--version TEXT``: Optionally a version filter. For instance 9 returns
- all versions 9.*, 9.1 returns 9.1.* etc.
-- ``--help``: print this help page.
diff --git a/docs/cli/index.rst b/docs/cli/index.rst
index 4e8c5f8881ae17..a5c15b82359c38 100644
--- a/docs/cli/index.rst
+++ b/docs/cli/index.rst
@@ -13,4 +13,4 @@ configuration via the ``SENTRY_CONF`` environment variable::
For a list of commands, you can also use ``sentry help``, or ``sentry
[command] --help`` for help on a specific command.
-.. include:: index.rst.inc
+.. include:: index.rst.inc
\ No newline at end of file
diff --git a/docs/cli/index.rst.inc b/docs/cli/index.rst.inc
index 8052921047baba..33885f886b30ac 100644
--- a/docs/cli/index.rst.inc
+++ b/docs/cli/index.rst.inc
@@ -22,14 +22,14 @@ Subcommands
:maxdepth: 1
files
- repair
+ plugins
upgrade
run
help
- exec
queues
- plugins
+ exec
devserver
+ repair
django
start
init
@@ -40,5 +40,4 @@ Subcommands
shell
config
tsdb
- dsym
diff --git a/docs/cli/run/cron/index.rst b/docs/cli/run/cron/index.rst
index eef5c2b5489401..9138681f32c838 100644
--- a/docs/cli/run/cron/index.rst
+++ b/docs/cli/run/cron/index.rst
@@ -14,6 +14,9 @@ Options
- ``--autoreload``: Enable autoreloading.
+
+
+
- ``-l, --loglevel [DEBUG|INFO|WARNING|ERROR|CRITICAL|FATAL]``: Global
logging level. Use wisely.
- ``--logformat [human|machine]``: Log line format.
diff --git a/docs/cli/run/worker/index.rst b/docs/cli/run/worker/index.rst
index 76a8b28d53faeb..126ecc8d63f65f 100644
--- a/docs/cli/run/worker/index.rst
+++ b/docs/cli/run/worker/index.rst
@@ -19,6 +19,10 @@ Options
- ``--autoreload``: Enable autoreloading.
+
+
+
+
- ``-l, --loglevel [DEBUG|INFO|WARNING|ERROR|CRITICAL|FATAL]``: Global
logging level. Use wisely.
- ``--logformat [human|machine]``: Log line format.
diff --git a/docs/conf.py b/docs/conf.py
index 28a71aa996679f..f0bc4d52227ff6 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -11,6 +11,7 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
+from __future__ import print_function
import sys
import os
@@ -242,4 +243,4 @@
import sentryext
sentryext.activate()
except ImportError:
- print 'ERROR: could not import sentryext. You need to check out the _sentryext submodule.'
+ print('ERROR: could not import sentryext. You need to check out the _sentryext submodule.')
diff --git a/docs/faq.rst b/docs/faq.rst
index 1c95e2f9a6277f..d52ad5b09dde02 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -73,6 +73,7 @@ and users?
project = Project()
project.team = team
+ project.add_team(team)
project.name = 'Default'
project.organization = organization
project.save()
diff --git a/docs/installation/python/index.rst b/docs/installation/python/index.rst
index af12a818d09f19..4448c8a57a7df6 100644
--- a/docs/installation/python/index.rst
+++ b/docs/installation/python/index.rst
@@ -21,7 +21,7 @@ Some basic prerequisites which you'll need in order to run Sentry:
If you're building from source you'll also need:
-* Node.js 4.0 or newer.
+* Node.js 8.0 or newer.
Setting up an Environment
-------------------------
@@ -111,7 +111,7 @@ Once your system is prepared, symlink your source into the virtualenv:
.. code-block:: bash
- $ python setup.py develop
+ $ pip install --editable .
.. Note:: This command will install npm dependencies as well as compile
static assets.
diff --git a/docs/sso.rst b/docs/sso.rst
index ca35178f4b3b3c..911399058f8f91 100644
--- a/docs/sso.rst
+++ b/docs/sso.rst
@@ -20,6 +20,13 @@ with a feature switch in your ``sentry.conf.py``::
# turn SSO on our off
SENTRY_FEATURES['organizations:sso'] = False
+Additionally you may enable advanced SSO features::
+
+ from sentry.conf.server import *
+
+ SENTRY_FEATURES['organizations:sso-saml2'] = True
+ SENTRY_FEATURES['organizations:sso-rippling'] = True
+
You should see an **Auth** subheading under your organization's dashboard when SSO is enabled.
Installing a Provider
diff --git a/examples/oauth2_consumer_webserver/app.py b/examples/oauth2_consumer_webserver/app.py
index b5ff286264b82d..0a69a7ab31bbe2 100644
--- a/examples/oauth2_consumer_webserver/app.py
+++ b/examples/oauth2_consumer_webserver/app.py
@@ -53,7 +53,7 @@ def index():
req = Request('{}/api/0/organizations/'.format(BASE_URL), None, headers)
try:
res = urlopen(req)
- except URLError, e:
+ except URLError as e:
if e.code == 401:
# Unauthorized - bad token
session.pop('access_token', None)
diff --git a/package.json b/package.json
index 0c5b25f1e1c2a8..a2519493b2c897 100644
--- a/package.json
+++ b/package.json
@@ -8,30 +8,39 @@
"dependencies": {
"babel-core": "6.21.0",
"babel-gettext-extractor": "^3.0.0",
- "babel-loader": "^7.0.0",
+ "babel-loader": "^7.1.2",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-dynamic-import-node": "^1.0.2",
+ "babel-plugin-emotion": "^8.0.2-11",
"babel-plugin-idx": "^1.5.1",
"babel-plugin-lodash": "^3.2.11",
"babel-plugin-transform-builtin-extend": "^1.1.0",
"babel-plugin-transform-class-properties": "^6.24.1",
+ "babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-object-rest-spread": "^6.20.2",
+ "babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "6.20.0",
"babel-preset-latest": "^6.16.0",
"babel-preset-react": "^6.16.0",
"bootstrap": "3.3.5",
"classnames": "2.2.0",
+ "clipboard": "^1.7.1",
"compression-webpack-plugin": "^1.0.0",
+ "create-react-class": "^15.6.2",
"crypto-js": "3.1.5",
"css-loader": "^0.28.2",
"diff": "^3.3.0",
- "enzyme-to-json": "^1.5.1",
- "extract-text-webpack-plugin": "2.0.0-beta.5",
+ "emotion": "^8.0.2-12",
+ "emotion-theming": "^8.0.2-10",
+ "enzyme-adapter-react-16": "^1.1.1",
+ "enzyme-to-json": "3.3.1",
+ "extract-text-webpack-plugin": "^3.0.0",
"file-loader": "0.8.4",
"gettext-parser": "1.1.1",
+ "grid-emotion": "^2.1.0",
"history": "1.13.0",
"idx": "^1.5.0",
- "ios-device-list": "^1.1.17",
+ "ios-device-list": "^1.1.28",
"jed": "^1.1.0",
"jquery": "2.1.4",
"js-cookie": "2.0.4",
@@ -40,62 +49,78 @@
"less-loader": "^4.0.3",
"lodash": "^4.17.4",
"lodash-webpack-plugin": "^0.11.4",
- "marked": "0.3.5",
- "moment": "2.10.6",
+ "marked": "0.3.9",
+ "mobx": "^3.2.2",
+ "mobx-react": "^4.2.2",
+ "mockdate": "2.0.2",
+ "moment": "2.19.3",
"moment-timezone": "0.4.1",
"node-libs-browser": "0.5.3",
"platformicons": "0.1.1",
"po-catalog-loader": "^1.2.0",
- "prop-types": "^15.5.10",
+ "prop-types": "^15.6.0",
"query-string": "2.4.2",
- "raven-js": "3.18.1",
- "react": "15.3.2",
- "react-addons-css-transition-group": "15.3.2",
- "react-addons-pure-render-mixin": "15.3.2",
- "react-addons-test-utils": "15.3.2",
- "react-addons-update": "15.3.2",
- "react-bootstrap": "^0.29.5",
- "react-code-input": "1.0.8",
- "react-document-title": "1.0.4",
- "react-dom": "15.3.2",
- "react-hot-loader": "^3.0.0-beta.7",
+ "raven-js": "3.19.1",
+ "react": "16.2.0",
+ "react-addons-css-transition-group": "15.6.2",
+ "react-bootstrap": "^0.32.0",
+ "react-document-title": "2.0.3",
+ "react-dom": "16.2.0",
+ "react-emotion": "^8.0.2-12 ",
+ "react-hot-loader": "^3.1.3",
"react-icon-base": "^2.0.4",
- "react-lazy-load": "3.0.10",
+ "react-lazy-load": "3.0.13",
"react-mentions": "^1.0.0",
- "react-router": "2.8.1",
- "react-sparklines": "1.6.0",
- "react-sticky": "5.0.4",
+ "react-router": "3.2.0",
+ "react-sparklines": "1.7.0",
+ "react-sticky": "6.0.1",
"reflux": "0.4.1",
+ "scroll-to-element": "^2.0.0",
"select2": "3.5.1",
"sprintf-js": "1.0.3",
"style-loader": "0.12.4",
+ "svg-sprite-loader": "^3.4.1",
+ "svgo": "^1.0.3",
+ "svgo-loader": "^2.1.0",
"u2f-api": "0.2.3",
"url-loader": "0.5.6",
- "webpack": "2.2.0",
- "webpack-dev-middleware": "1.9.0",
+ "webpack": "^3.5.6",
+ "webpack-dev-middleware": "^1.12.0",
"zxcvbn": "^4.4.2"
},
"private": true,
"APIMethod": "stub",
"proxyURL": "http://localhost:8000",
"scripts": {
- "test": "npm run jest -- tests/js/spec",
- "test-ci": "npm run test -- --runInBand",
+ "test": "node scripts/test.js",
+ "test-ci": "npm run test -- --runInBand --ci --coverage --testResultsProcessor=jest-junit",
"test-watch": "npm run test -- --watch",
- "jest": "NODE_ENV=test node_modules/.bin/jest",
- "lint": "node_modules/.bin/eslint tests/js src/sentry/static/sentry/app --ext .jsx",
+ "jest": "node scripts/test.js",
+ "jest-debug": "node --inspect-brk scripts/test.js --runInBand",
+ "lint": "node_modules/.bin/eslint tests/js src/sentry/static/sentry/app --ext .js,.jsx",
"dev-proxy": "node scripts/devproxy.js",
"dev-server": "webpack-dev-server",
"storybook": "start-storybook -p 9001 -c .storybook",
- "snapshot": "build-storybook && PERCY_TOKEN=$STORYBOOK_PERCY_TOKEN PERCY_PROJECT=$STORYBOOK_PERCY_PROJECT percy-storybook --widths=375,1280"
+ "snapshot": "build-storybook && PERCY_TOKEN=$STORYBOOK_PERCY_TOKEN PERCY_PROJECT=$STORYBOOK_PERCY_PROJECT percy-storybook --widths=375,1280",
+ "webpack-profile": "yarn -s webpack --profile --json > stats.json"
},
"jest": {
+ "collectCoverageFrom": [
+ "tests/js/spec/**/*.{js,jsx}",
+ "src/sentry/static/sentry/app/**/*.{js,jsx}"
+ ],
+ "coverageReporters": [
+ "lcov",
+ "cobertura"
+ ],
"snapshotSerializers": [
"enzyme-to-json/serializer"
],
"moduleNameMapper": {
- "\\.(css|less)$": "/tests/js/helpers/importStyleMock.js",
- "integration-docs-platforms": "/tests/fixtures/_platforms.json"
+ "\\.(css|less|png)$": "/tests/js/helpers/importStyleMock.js",
+ "\\.(svg)$": "/tests/js/helpers/svgMock.js",
+ "jquery": "/src/sentry/static/sentry/__mocks__/jquery.jsx",
+ "integration-docs-platforms": "/tests/fixtures/integration-docs/_platforms.json"
},
"modulePaths": [
"/src/sentry/static/sentry"
@@ -103,6 +128,10 @@
"setupFiles": [
"/tests/js/setup.js"
],
+ "setupTestFrameworkScriptFile": "/tests/js/setupFramework.js",
+ "testMatch": [
+ "/tests/js/**/?(*.)(spec|test).js?(x)"
+ ],
"testPathIgnorePatterns": [
"/tests/sentry/lang/javascript/"
],
@@ -112,23 +141,27 @@
]
},
"devDependencies": {
- "@percy-io/react-percy-storybook": "^1.0.2",
- "@storybook/addon-actions": "^3.2.11",
- "@storybook/addon-info": "^3.2.11",
- "@storybook/addon-knobs": "^3.2.10",
- "@storybook/react": "^3.2.11",
- "babel-eslint": "7.1.1",
- "babel-jest": "^19.0.0",
+ "@percy-io/react-percy-storybook": "^1.1.5",
+ "@storybook/addon-actions": "^3.2.17",
+ "@storybook/addon-info": "^3.2.17",
+ "@storybook/addon-knobs": "^3.2.17",
+ "@storybook/react": "^3.2.17",
+ "babel-eslint": "7.2.3",
+ "babel-jest": "22.1.0",
"chai": "3.4.1",
- "enzyme": "2.9.1",
- "eslint": "3.19.0",
+ "enzyme": "3.2.0",
+ "eslint": "4.4.1",
+ "eslint-config-prettier": "^2.3.0",
+ "eslint-config-sentry-react": "1.0.4",
"eslint-import-resolver-webpack": "^0.8.3",
"eslint-plugin-getsentry": "1.0.0",
"eslint-plugin-import": "^2.7.0",
- "eslint-plugin-react": "6.10.3",
- "jest": "^19.0.2",
- "phantomjs-prebuilt": "2.1.14",
- "prettier": "1.2.2",
+ "eslint-plugin-react": "7.4.0",
+ "jest": "22.1.2",
+ "jest-glamor-react": "^3.1.2",
+ "jest-junit": "^3.4.1",
+ "prettier": "1.7.4",
+ "react-test-renderer": "16.2.0",
"sinon": "1.17.2",
"sinon-chai": "2.8.0",
"webpack-dev-server": "^2.7.1"
diff --git a/requirements-base.txt b/requirements-base.txt
index e3ad326b65ad81..3e61451e834531 100644
--- a/requirements-base.txt
+++ b/requirements-base.txt
@@ -12,24 +12,25 @@ django-sudo>=2.1.0,<3.0.0
django-templatetag-sugar>=0.1.0
djangorestframework>=2.4.8,<2.5.0
email-reply-parser>=0.2.0,<0.3.0
-enum34>=0.9.18,<1.2.0
+enum34>=1.1.6,<1.2.0
exam>=0.5.1
+functools32>=3.2.3,<3.3
# broken on python3
hiredis>=0.1.0,<0.2.0
-honcho>=0.7.0,<0.8.0
+honcho>=1.0.0,<1.1.0
kombu==3.0.35
ipaddress>=1.0.16,<1.1.0
-libsourcemap>=0.8.2,<0.9.0
loremipsum>=1.0.5,<1.1.0
+jsonschema==2.6.0
lxml>=3.4.1
mock>=0.8.0,<1.1
mmh3>=2.3.1,<2.4
oauth2>=1.5.167
percy>=0.4.5
petname>=2.0,<2.1
-Pillow>=3.2.0,<3.3.0
+Pillow>=3.2.0,<=4.2.1
progressbar2>=3.10,<3.11
-psycopg2>=2.6.0,<2.7.0
+psycopg2>=2.6.0,<2.8.0
pytest>=3.1.2,<3.2.0
pytest-django>=2.9.1,<2.10.0
pytest-html>=1.9.0,<1.10.0
@@ -37,17 +38,19 @@ python-dateutil>=2.0.0,<3.0.0
python-memcached>=1.53,<2.0.0
python-openid>=2.2
PyYAML>=3.11,<3.12
-raven>=5.29.0,<6.0.0
+querystring_parser>=1.2.3,<2.0.0
+raven>=6.0.0,<=6.4.0
redis>=2.10.3,<2.10.6
requests[security]>=2.18.4,<2.19.0
-selenium==3.4.3
+selenium==3.8.0
simplejson>=3.2.0,<3.9.0
six>=1.10.0,<1.11.0
setproctitle>=1.1.7,<1.2.0
statsd>=3.1.0,<3.2.0
+strict-rfc3339>=0.7
structlog==16.1.0
sqlparse>=0.1.16,<0.2.0
-symsynd>=3.0.0,<4.0.0
+symbolic>=2.0.4,<3.0.0
toronado>=0.0.11,<0.1.0
ua-parser>=0.6.1,<0.8.0
urllib3>=1.22,<1.23
@@ -56,3 +59,4 @@ rb>=1.7.0,<2.0.0
qrcode>=5.2.2,<6.0.0
python-u2flib-server>=4.0.1,<4.1.0
redis-py-cluster>=1.3.4,<1.4.0
+jsonschema==2.6.0
diff --git a/requirements-optional.txt b/requirements-optional.txt
index 7224ceb46c8c5b..bf6a24cd5b9f1c 100644
--- a/requirements-optional.txt
+++ b/requirements-optional.txt
@@ -1 +1,4 @@
+google-cloud-pubsub>=0.28.3,<0.29
+# See https://github.com/GoogleCloudPlatform/google-cloud-python/issues/4001
+grpcio==1.4.0
python3-saml>=1.2.6,<1.3
diff --git a/requirements-test.txt b/requirements-test.txt
index 0bc9051e733559..38b4319d1bc0af 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -10,4 +10,4 @@ msgpack-python<0.5.0
pytest-cov>=2.5.1,<2.6.0
pytest-timeout>=0.5.0,<0.6.0
pytest-xdist>=1.18.0,<1.19.0
-responses>=0.8.0,<0.9.0
+responses>=0.8.1,<0.9.0
diff --git a/scripts/Dockerfile b/scripts/Dockerfile
deleted file mode 100644
index 85004c5946639a..00000000000000
--- a/scripts/Dockerfile
+++ /dev/null
@@ -1,48 +0,0 @@
-FROM python:2.7.11-slim
-
-RUN apt-get update && apt-get install -y --no-install-recommends \
- curl \
- gcc \
- git \
- libffi-dev \
- libpq-dev \
- libxml2-dev \
- libxslt-dev \
- libyaml-dev \
- && rm -rf /var/lib/apt/lists/*
-
-# Sane defaults for pip
-ENV PIP_NO_CACHE_DIR off
-ENV PIP_DISABLE_PIP_VERSION_CHECK on
-
-# gpg keys listed at https://github.com/nodejs/node
-RUN set -ex \
- && for key in \
- 9554F04D7259F04124DE6B476D5A82AC7E37093B \
- 94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
- 0034A06D9D9B0064CE8ADF6BF1747F4AD2306D93 \
- FD3A5288F042B6850C66B31F09FE44734EB7990E \
- 71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
- DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
- B9AE9905FFD7803F25714661B63B535A4C206CA9 \
- C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
- ; do \
- gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
- done
-
-ENV NODE_VERSION 0.12.12
-ENV NPM_VERSION 2.15.1
-
-RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz" \
- && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
- && gpg --verify SHASUMS256.txt.asc \
- && grep " node-v$NODE_VERSION-linux-x64.tar.gz\$" SHASUMS256.txt.asc | sha256sum -c - \
- && tar -xzf "node-v$NODE_VERSION-linux-x64.tar.gz" -C /usr/local --strip-components=1 \
- && rm "node-v$NODE_VERSION-linux-x64.tar.gz" SHASUMS256.txt.asc \
- && npm install -g "npm@${NPM_VERSION}" \
- && npm cache clear
-
-WORKDIR /usr/src/sentry
-
-CMD python setup.py sdist bdist_wheel \
- && mv dist/* /dist/
diff --git a/scripts/devproxy.js b/scripts/devproxy.js
index b80cb62ffecd04..57f7c9a53663de 100644
--- a/scripts/devproxy.js
+++ b/scripts/devproxy.js
@@ -15,6 +15,10 @@ if (!WEBPACK_DEV_PORT || !WEBPACK_DEV_PROXY || !SENTRY_DEVSERVER_PORT) {
}
const createProxy = function(proxy, req, res, port, cb) {
+ if (res.headersSent) {
+ return;
+ }
+
proxy.web(req, res, {target: 'http://localhost:' + port}, function(e, r) {
cb && cb(e, r);
if (e) {
@@ -43,4 +47,9 @@ const server = http.createServer(function(req, res) {
console.log('Proxy target not responding');
}
});
+
+server.on('error', function() {
+ console.log('devproxy error', arguments);
+});
+
server.listen(WEBPACK_DEV_PROXY);
diff --git a/scripts/docker-entrypoint.sh b/scripts/docker-entrypoint.sh
deleted file mode 100755
index b0ef5cf9a1e9f6..00000000000000
--- a/scripts/docker-entrypoint.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-set -ex
-
-# Make sure to always start all the background services
-service postgresql start
-service redis-server start
-service memcached start
-service postfix start
-service ntp start
-
-if [ ! -f /.bootstrapped ]; then
- SENTRY_LIGHT_BUILD=1 pip install -vvv -e .[dev,tests]
- npm install
- sentry init $SENTRY_CONF
- sentry upgrade --noinput
- sentry createuser --email=root@localhost --password=admin --superuser --no-input
- touch /.bootstrapped
-
- echo "done" && exit 0
-fi
-
-exec "$@"
diff --git a/scripts/sdist b/scripts/sdist
deleted file mode 100755
index 686880fa6b02af..00000000000000
--- a/scripts/sdist
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-set -ex
-
-ARTIFACTS_PATH=${ARTIFACTS_PATH:-dist}
-CLONE_PATH=${CLONE_PATH:-${PWD}}
-COMMIT=${COMMIT:-UNKNOWN}
-
-docker build --rm -t sentry-release -f scripts/Dockerfile .
-docker run --rm -v ${ARTIFACTS_PATH}:/dist -v ${CLONE_PATH}:/usr/src/sentry -e SENTRY_BUILD=${COMMIT} sentry-release
diff --git a/scripts/test.js b/scripts/test.js
new file mode 100644
index 00000000000000..cc424cd81468e0
--- /dev/null
+++ b/scripts/test.js
@@ -0,0 +1,24 @@
+'use strict';
+
+// Do this as the first thing so that any code reading it knows the right env.
+// process.env.BABEL_ENV = 'test';
+process.env.NODE_ENV = 'test';
+process.env.PUBLIC_URL = '';
+process.env.TZ = 'America/New_York';
+
+// Makes the script crash on unhandled rejections instead of silently
+// ignoring them. In the future, promise rejections that are not handled will
+// terminate the Node.js process with a non-zero exit code.
+process.on('unhandledRejection', err => {
+ throw err;
+});
+
+const jest = require('jest');
+const argv = process.argv.slice(2);
+
+// Watch unless on CI or in coverage mode
+if (!process.env.CI && argv.indexOf('--coverage') < 0) {
+ argv.push('--watch');
+}
+
+jest.run(argv);
diff --git a/setup.cfg b/setup.cfg
index 5cbfbdecafa957..6f7c41192c50f1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,10 +1,9 @@
-[pytest]
+[tool:pytest]
python_files = test*.py
-addopts = --tb=native -p no:doctest
+addopts = --tb=native -p no:doctest -p no:warnings
norecursedirs = bin dist docs htmlcov script hooks node_modules .* {args}
looponfailroots = src tests
-selenium_driver = phantomjs
-phantomjs_path = node_modules/phantomjs-prebuilt/bin/phantomjs
+selenium_driver = chrome
[flake8]
ignore = F999,E501,E128,E124,E402,W503,E731,C901
diff --git a/setup.py b/setup.py
index fb73686c2ae4ce..555ca33f5270a8 100755
--- a/setup.py
+++ b/setup.py
@@ -48,7 +48,7 @@
)
# The version of sentry
-VERSION = '8.22.0.dev0'
+VERSION = '8.23.0.dev0'
# Hack to prevent stupid "TypeError: 'NoneType' object is not callable" error
# in multiprocessing/util.py _exit_function when running `python
diff --git a/src/sentry/analytics/base.py b/src/sentry/analytics/base.py
index 4bc7daa49a652c..6129d671a0cf70 100644
--- a/src/sentry/analytics/base.py
+++ b/src/sentry/analytics/base.py
@@ -4,6 +4,7 @@
import six
+from sentry.analytics.event import Event
from sentry.utils.services import Service
from .event_manager import default_manager
@@ -23,8 +24,10 @@ def record(self, event_or_event_type, instance=None, **kwargs):
event = self.event_manager.get(
event_or_event_type,
).from_instance(instance, **kwargs)
+ elif isinstance(event_or_event_type, Event):
+ event = event_or_event_type.from_instance(instance, **kwargs)
else:
- event = event_or_event_type
+ return
self.record_event(event)
def record_event(self, event):
diff --git a/src/sentry/analytics/event.py b/src/sentry/analytics/event.py
index c09861efed38fc..b0d2dba1738d8f 100644
--- a/src/sentry/analytics/event.py
+++ b/src/sentry/analytics/event.py
@@ -3,10 +3,14 @@
__all__ = ('Attribute', 'Event', 'Map')
import six
+from uuid import uuid1
+from base64 import b64encode
from collections import Mapping
from django.utils import timezone
+from sentry.utils.dates import to_timestamp
+
class Attribute(object):
def __init__(self, name, type=six.text_type, required=True):
@@ -68,13 +72,15 @@ def extract(self, value):
class Event(object):
- __slots__ = ['attributes', 'data', 'datetime', 'type']
+ __slots__ = ['uuid', 'attributes', 'data', 'datetime', 'type']
type = None
attributes = ()
def __init__(self, type=None, datetime=None, **items):
+ self.uuid = uuid1()
+
self.datetime = datetime or timezone.now()
if type is not None:
self.type = type
@@ -99,16 +105,16 @@ def __init__(self, type=None, datetime=None, **items):
self.data = data
def serialize(self):
- return dict(
- {
- 'timestamp': int(self.datetime.isoformat('%s')),
- 'type': self.type,
- }, **self.data
- )
+ return {
+ 'uuid': b64encode(self.uuid.bytes),
+ 'timestamp': to_timestamp(self.datetime),
+ 'type': self.type,
+ 'data': self.data,
+ }
@classmethod
def from_instance(cls, instance, **kwargs):
values = {}
for attr in cls.attributes:
- values[attr.name] = (kwargs.get(attr.name) or getattr(instance, attr.name, None))
+ values[attr.name] = kwargs.get(attr.name, getattr(instance, attr.name, None))
return cls(**values)
diff --git a/src/sentry/analytics/pubsub.py b/src/sentry/analytics/pubsub.py
new file mode 100644
index 00000000000000..0e840a97639933
--- /dev/null
+++ b/src/sentry/analytics/pubsub.py
@@ -0,0 +1,26 @@
+from __future__ import absolute_import
+
+__all__ = ('PubSubAnalytics',)
+
+from sentry.utils.json import dumps
+from google.cloud import pubsub_v1
+
+from .base import Analytics
+
+
+class PubSubAnalytics(Analytics):
+ def __init__(self, project, topic, batch_max_bytes=1024 * 1024 *
+ 5, batch_max_latency=0.05, batch_max_messages=1000):
+ settings = pubsub_v1.types.BatchSettings(
+ max_bytes=batch_max_bytes,
+ max_latency=batch_max_latency,
+ max_messages=batch_max_messages,
+ )
+ self.publisher = pubsub_v1.PublisherClient(settings)
+ self.topic = self.publisher.topic_path(project, topic)
+
+ def record_event(self, event):
+ self.publisher.publish(
+ self.topic,
+ data=dumps(event.serialize()),
+ )
diff --git a/src/sentry/api/base.py b/src/sentry/api/base.py
index 77a5960d78137b..d86fa97a9d7b6e 100644
--- a/src/sentry/api/base.py
+++ b/src/sentry/api/base.py
@@ -1,5 +1,6 @@
from __future__ import absolute_import
+import functools
import logging
import six
import time
@@ -18,16 +19,18 @@
from sentry import tsdb
from sentry.app import raven
-from sentry.models import ApiKey, AuditLogEntry
+from sentry.models import Environment
from sentry.utils.cursors import Cursor
from sentry.utils.dates import to_datetime
from sentry.utils.http import absolute_uri, is_valid_origin
+from sentry.utils.audit import create_audit_entry
from .authentication import ApiKeyAuthentication, TokenAuthentication
from .paginator import Paginator
from .permissions import NoPermission
-__all__ = ['DocSection', 'Endpoint', 'StatsMixin']
+
+__all__ = ['DocSection', 'Endpoint', 'EnvironmentMixin', 'StatsMixin']
ONE_MINUTE = 60
ONE_HOUR = ONE_MINUTE * 60
@@ -35,7 +38,8 @@
LINK_HEADER = '<{uri}&cursor={cursor}>; rel="{name}"; results="{has_results}"; cursor="{cursor}"'
-DEFAULT_AUTHENTICATION = (TokenAuthentication, ApiKeyAuthentication, SessionAuthentication, )
+DEFAULT_AUTHENTICATION = (
+ TokenAuthentication, ApiKeyAuthentication, SessionAuthentication, )
logger = logging.getLogger(__name__)
audit_logger = logging.getLogger('sentry.audit.api')
@@ -92,35 +96,7 @@ def handle_exception(self, request, exc):
return Response(context, status=500)
def create_audit_entry(self, request, transaction_id=None, **kwargs):
- user = request.user if request.user.is_authenticated() else None
- api_key = request.auth if isinstance(request.auth, ApiKey) else None
-
- entry = AuditLogEntry(
- actor=user, actor_key=api_key, ip_address=request.META['REMOTE_ADDR'], **kwargs
- )
-
- # Only create a real AuditLogEntry record if we are passing an event type
- # otherwise, we want to still log to our actual logging
- if entry.event is not None:
- entry.save()
-
- extra = {
- 'ip_address': entry.ip_address,
- 'organization_id': entry.organization_id,
- 'object_id': entry.target_object,
- 'entry_id': entry.id,
- 'actor_label': entry.actor_label
- }
- if entry.actor_id:
- extra['actor_id'] = entry.actor_id
- if entry.actor_key_id:
- extra['actor_key_id'] = entry.actor_key_id
- if transaction_id is not None:
- extra['transaction_id'] = transaction_id
-
- audit_logger.info(entry.get_event_display(), extra=extra)
-
- return entry
+ return create_audit_entry(request, transaction_id, audit_logger, **kwargs)
def initialize_request(self, request, *args, **kwargs):
rv = super(Endpoint, self).initialize_request(request, *args, **kwargs)
@@ -158,15 +134,25 @@ def dispatch(self, request, *args, **kwargs):
if origin and request.auth:
allowed_origins = request.auth.get_allowed_origins()
if not is_valid_origin(origin, allowed=allowed_origins):
- response = Response('Invalid origin: %s' % (origin, ), status=400)
- self.response = self.finalize_response(request, response, *args, **kwargs)
+ response = Response('Invalid origin: %s' %
+ (origin, ), status=400)
+ self.response = self.finalize_response(
+ request, response, *args, **kwargs)
return self.response
self.initial(request, *args, **kwargs)
+ if getattr(request, 'user', None) and request.user.is_authenticated():
+ raven.user_context({
+ 'id': request.user.id,
+ 'username': request.user.username,
+ 'email': request.user.email,
+ })
+
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
- handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
+ handler = getattr(self, request.method.lower(),
+ self.http_method_not_allowed)
(args, kwargs) = self.convert_args(request, *args, **kwargs)
self.args = args
@@ -182,13 +168,15 @@ def dispatch(self, request, *args, **kwargs):
if origin:
self.add_cors_headers(request, response)
- self.response = self.finalize_response(request, response, *args, **kwargs)
+ self.response = self.finalize_response(
+ request, response, *args, **kwargs)
return self.response
def add_cors_headers(self, request, response):
response['Access-Control-Allow-Origin'] = request.META['HTTP_ORIGIN']
- response['Access-Control-Allow-Methods'] = ', '.join(self.http_method_names)
+ response['Access-Control-Allow-Methods'] = ', '.join(
+ self.http_method_names)
def add_cursor_headers(self, request, response, cursor_result):
if cursor_result.hits is not None:
@@ -197,7 +185,8 @@ def add_cursor_headers(self, request, response, cursor_result):
response['X-Max-Hits'] = cursor_result.max_hits
response['Link'] = ', '.join(
[
- self.build_cursor_link(request, 'previous', cursor_result.prev),
+ self.build_cursor_link(
+ request, 'previous', cursor_result.prev),
self.build_cursor_link(request, 'next', cursor_result.next),
]
)
@@ -232,8 +221,48 @@ def paginate(
return response
+class EnvironmentMixin(object):
+ def _get_environment_func(self, request, organization_id):
+ """\
+ Creates a function that when called returns the ``Environment``
+ associated with a request object, or ``None`` if no environment was
+ provided. If the environment doesn't exist, an ``Environment.DoesNotExist``
+ exception will be raised.
+
+ This returns as a callable since some objects outside of the API
+ endpoint need to handle the "environment was provided but does not
+ exist" state in addition to the two non-exceptional states (the
+ environment was provided and exists, or the environment was not
+ provided.)
+ """
+ return functools.partial(
+ self._get_environment_from_request,
+ request,
+ organization_id,
+ )
+
+ def _get_environment_id_from_request(self, request, organization_id):
+ environment = self._get_environment_from_request(request, organization_id)
+ return environment and environment.id
+
+ def _get_environment_from_request(self, request, organization_id):
+ if not hasattr(request, '_cached_environment'):
+ environment_param = request.GET.get('environment')
+ if environment_param is None:
+ environment = None
+ else:
+ environment = Environment.get_for_organization_id(
+ name=environment_param,
+ organization_id=organization_id,
+ )
+
+ request._cached_environment = environment
+
+ return request._cached_environment
+
+
class StatsMixin(object):
- def _parse_args(self, request):
+ def _parse_args(self, request, environment_id=None):
resolution = request.GET.get('resolution')
if resolution:
resolution = self._parse_resolution(resolution)
@@ -256,6 +285,7 @@ def _parse_args(self, request):
'start': start,
'end': end,
'rollup': resolution,
+ 'environment_id': environment_id,
}
def _parse_resolution(self, value):
diff --git a/src/sentry/api/bases/organization.py b/src/sentry/api/bases/organization.py
index a09170e87cfd57..fa856b34db27ff 100644
--- a/src/sentry/api/bases/organization.py
+++ b/src/sentry/api/bases/organization.py
@@ -7,8 +7,9 @@
from sentry.api.permissions import ScopedPermission
from sentry.app import raven
from sentry.auth import access
+from sentry.auth.superuser import is_active_superuser
from sentry.models import (
- ApiKey, Organization, OrganizationMemberTeam, OrganizationStatus, Project, ReleaseProject, Team
+ ApiKey, Authenticator, Organization, OrganizationMemberTeam, Project, ProjectTeam, ReleaseProject, Team
)
from sentry.utils import auth
@@ -21,6 +22,9 @@ class OrganizationPermission(ScopedPermission):
'DELETE': ['org:admin'],
}
+ def is_not_2fa_compliant(self, user, organization):
+ return organization.flags.require_2fa and not Authenticator.objects.user_has_2fa(user)
+
def needs_sso(self, request, organization):
# XXX(dcramer): this is very similar to the server-rendered views
# logic for checking valid SSO
@@ -56,16 +60,30 @@ def has_object_permission(self, request, view, organization):
'user_id': request.user.id,
}
)
- elif request.user.is_authenticated() and self.needs_sso(request, organization):
+ elif request.user.is_authenticated():
# session auth needs to confirm various permissions
- logger.info(
- 'access.must-sso',
- extra={
- 'organization_id': organization.id,
- 'user_id': request.user.id,
- }
- )
- raise NotAuthenticated(detail='Must login via SSO')
+ if self.needs_sso(request, organization):
+
+ logger.info(
+ 'access.must-sso',
+ extra={
+ 'organization_id': organization.id,
+ 'user_id': request.user.id,
+ }
+ )
+ raise NotAuthenticated(detail='Must login via SSO')
+
+ if self.is_not_2fa_compliant(
+ request.user, organization):
+ logger.info(
+ 'access.not-2fa-compliant',
+ extra={
+ 'organization_id': organization.id,
+ 'user_id': request.user.id,
+ }
+ )
+ raise NotAuthenticated(
+ detail='Organization requires two-factor authentication to be enabled')
allowed_scopes = set(self.scope_map.get(request.method, []))
return any(request.access.has_scope(s) for s in allowed_scopes)
@@ -92,6 +110,24 @@ class OrganizationIntegrationsPermission(OrganizationPermission):
}
+class OrganizationAdminPermission(OrganizationPermission):
+ scope_map = {
+ 'GET': ['org:admin'],
+ 'POST': ['org:admin'],
+ 'PUT': ['org:admin'],
+ 'DELETE': ['org:admin'],
+ }
+
+
+class OrganizationAuthProviderPermission(OrganizationPermission):
+ scope_map = {
+ 'GET': ['org:read'],
+ 'POST': ['org:admin'],
+ 'PUT': ['org:admin'],
+ 'DELETE': ['org:admin'],
+ }
+
+
class OrganizationEndpoint(Endpoint):
permission_classes = (OrganizationPermission, )
@@ -103,9 +139,6 @@ def convert_args(self, request, organization_slug, *args, **kwargs):
except Organization.DoesNotExist:
raise ResourceDoesNotExist
- if organization.status != OrganizationStatus.VISIBLE:
- raise ResourceDoesNotExist
-
self.check_object_permissions(request, organization)
raven.tags_context({
@@ -130,7 +163,7 @@ def get_allowed_projects(self, request, organization):
if not (has_valid_api_key or request.user.is_authenticated()):
return []
- if has_valid_api_key or request.is_superuser() or organization.flags.allow_joinleave:
+ if has_valid_api_key or is_active_superuser(request) or organization.flags.allow_joinleave:
allowed_teams = Team.objects.filter(organization=organization).values_list(
'id', flat=True
)
@@ -141,7 +174,12 @@ def get_allowed_projects(self, request, organization):
).values_list(
'team_id', flat=True
)
- return Project.objects.filter(team_id__in=allowed_teams)
+
+ return Project.objects.filter(
+ id__in=ProjectTeam.objects.filter(
+ team_id__in=allowed_teams,
+ ).values_list('project_id', flat=True)
+ )
def has_release_permission(self, request, organization, release):
return ReleaseProject.objects.filter(
diff --git a/src/sentry/api/bases/organizationissues.py b/src/sentry/api/bases/organizationissues.py
index ce48abca2e98aa..2b29acde26ad14 100644
--- a/src/sentry/api/bases/organizationissues.py
+++ b/src/sentry/api/bases/organizationissues.py
@@ -2,6 +2,7 @@
from rest_framework.response import Response
+from sentry.api.base import EnvironmentMixin
from sentry.api.serializers import serialize, StreamGroupSerializer
from sentry.api.paginator import OffsetPaginator
from sentry.models import (Group, GroupStatus, OrganizationMemberTeam, Project, ProjectStatus)
@@ -11,7 +12,7 @@
ERR_INVALID_STATS_PERIOD = "Invalid stats_period. Valid choices are '', '24h', and '14d'"
-class OrganizationIssuesEndpoint(OrganizationMemberEndpoint):
+class OrganizationIssuesEndpoint(OrganizationMemberEndpoint, EnvironmentMixin):
def get_queryset(self, request, organization, member, project_list):
# Must return a 'sorty_by' selector for pagination that is a datetime
return Group.objects.none()
@@ -32,9 +33,9 @@ def get(self, request, organization, member):
project_list = Project.objects.filter(
organization=organization,
- team__in=OrganizationMemberTeam.objects.filter(
+ teams__in=OrganizationMemberTeam.objects.filter(
organizationmember=member,
- ).values('team')
+ ).values('team'),
)
queryset = self.get_queryset(request, organization, member, project_list)
@@ -51,22 +52,23 @@ def get(self, request, organization, member):
project__status=ProjectStatus.VISIBLE,
)
+ def on_results(results):
+ results = serialize(
+ results, request.user, StreamGroupSerializer(
+ environment_func=self._get_environment_func(request, organization.id),
+ stats_period=stats_period,
+ )
+ )
+
+ if request.GET.get('status') == 'unresolved':
+ results = [r for r in results if r['status'] == 'unresolved']
+
+ return results
+
return self.paginate(
request=request,
queryset=queryset,
order_by='-sort_by',
paginator_cls=OffsetPaginator,
- on_results=lambda x: self._on_results(request, x, stats_period),
+ on_results=on_results,
)
-
- def _on_results(self, request, results, stats_period):
- results = serialize(
- results, request.user, StreamGroupSerializer(
- stats_period=stats_period,
- )
- )
-
- if request.GET.get('status') == 'unresolved':
- results = [r for r in results if r['status'] == 'unresolved']
-
- return results
diff --git a/src/sentry/api/bases/project.py b/src/sentry/api/bases/project.py
index 748219902abcd5..26c9081f559b2d 100644
--- a/src/sentry/api/bases/project.py
+++ b/src/sentry/api/bases/project.py
@@ -5,10 +5,11 @@
from sentry.app import raven
from sentry.models import Project, ProjectStatus
-from .team import TeamPermission
+from .organization import OrganizationPermission
+from .team import has_team_permission
-class ProjectPermission(TeamPermission):
+class ProjectPermission(OrganizationPermission):
scope_map = {
'GET': ['project:read', 'project:write', 'project:admin'],
'POST': ['project:write', 'project:admin'],
@@ -17,7 +18,15 @@ class ProjectPermission(TeamPermission):
}
def has_object_permission(self, request, view, project):
- return super(ProjectPermission, self).has_object_permission(request, view, project.team)
+ result = super(ProjectPermission,
+ self).has_object_permission(request, view, project.organization)
+
+ if not result:
+ return result
+
+ return any(
+ has_team_permission(request, team, self.scope_map) for team in project.teams.all()
+ )
class StrictProjectPermission(ProjectPermission):
@@ -74,15 +83,13 @@ def convert_args(self, request, organization_slug, project_slug, *args, **kwargs
project = Project.objects.filter(
organization__slug=organization_slug,
slug=project_slug,
- ).select_related('organization', 'team').get()
+ ).select_related('organization').prefetch_related('teams').get()
except Project.DoesNotExist:
raise ResourceDoesNotExist
if project.status != ProjectStatus.VISIBLE:
raise ResourceDoesNotExist
- project.team.organization = project.organization
-
self.check_object_permissions(request, project)
raven.tags_context({
diff --git a/src/sentry/api/bases/team.py b/src/sentry/api/bases/team.py
index 9f3beea110f908..2ed2b0c051fc90 100644
--- a/src/sentry/api/bases/team.py
+++ b/src/sentry/api/bases/team.py
@@ -8,6 +8,14 @@
from .organization import OrganizationPermission
+def has_team_permission(request, team, scope_map):
+ if not (request.user and request.user.is_authenticated()) and request.auth:
+ return request.auth.organization_id == team.organization.id
+
+ allowed_scopes = set(scope_map.get(request.method, []))
+ return any(request.access.has_team_scope(team, s) for s in allowed_scopes)
+
+
class TeamPermission(OrganizationPermission):
scope_map = {
'GET': ['team:read', 'team:write', 'team:admin'],
@@ -22,11 +30,7 @@ def has_object_permission(self, request, view, team):
if not result:
return result
- if not (request.user and request.user.is_authenticated()) and request.auth:
- return request.auth.organization_id == team.organization.id
-
- allowed_scopes = set(self.scope_map.get(request.method, []))
- return any(request.access.has_team_scope(team, s) for s in allowed_scopes)
+ return has_team_permission(request, team, self.scope_map)
class TeamEndpoint(Endpoint):
diff --git a/src/sentry/api/bases/user.py b/src/sentry/api/bases/user.py
index 17079f196440be..a1aa7127663958 100644
--- a/src/sentry/api/bases/user.py
+++ b/src/sentry/api/bases/user.py
@@ -4,6 +4,7 @@
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.api.permissions import ScopedPermission
from sentry.models import User
+from sentry.auth.superuser import is_active_superuser
class UserPermission(ScopedPermission):
@@ -14,7 +15,7 @@ def has_object_permission(self, request, view, user=None):
return True
if request.auth:
return False
- if request.is_superuser():
+ if is_active_superuser(request):
return True
return False
diff --git a/src/sentry/api/client.py b/src/sentry/api/client.py
index cf9c3d30c85bbe..e516d19099dc7b 100644
--- a/src/sentry/api/client.py
+++ b/src/sentry/api/client.py
@@ -5,6 +5,7 @@
from django.core.urlresolvers import resolve
from rest_framework.test import APIRequestFactory, force_authenticate
+from sentry.auth.superuser import Superuser
from sentry.utils import json
from sentry.utils.compat import implements_to_string
@@ -39,7 +40,10 @@ def request(
is_superuser=None,
request=None
):
- full_path = self.prefix + path
+ if self.prefix not in path:
+ full_path = self.prefix + path
+ else:
+ full_path = path
# we explicitly do not allow you to override the request *and* the user
# as then other checks like is_superuser would need overwritten
@@ -65,18 +69,20 @@ def request(
mock_request.is_sudo = lambda: request.is_sudo()
else:
mock_request.is_sudo = lambda: is_sudo
+ mock_request.session = request.session
if is_superuser is None:
- mock_request.is_superuser = lambda: request.is_superuser()
+ mock_request.superuser = request.superuser
else:
- mock_request.is_superuser = lambda: is_superuser
- mock_request.session = request.session
+ mock_request.superuser = Superuser(mock_request)
else:
mock_request.auth = auth
mock_request.user = user
mock_request.is_sudo = lambda: is_sudo
- mock_request.is_superuser = lambda: is_superuser
mock_request.session = {}
+ mock_request.superuser = Superuser(mock_request)
+
+ mock_request.is_superuser = lambda: mock_request.superuser.is_active
if request:
# superuser checks require access to IP
diff --git a/src/sentry/api/decorators.py b/src/sentry/api/decorators.py
index 499cdfd0d83a18..6c83b970e25e50 100644
--- a/src/sentry/api/decorators.py
+++ b/src/sentry/api/decorators.py
@@ -1,17 +1,19 @@
from __future__ import absolute_import
-import json
-
-from django.http import HttpResponse
+from rest_framework.response import Response
from functools import wraps
from sentry.models import ApiKey, ApiToken
def is_considered_sudo(request):
+ # Users without a password are assumed to always have sudo powers
+ user = request.user
+
return request.is_sudo() or \
isinstance(request.auth, ApiKey) or \
- isinstance(request.auth, ApiToken)
+ isinstance(request.auth, ApiToken) or \
+ user.is_authenticated() and not user.has_usable_password()
def sudo_required(func):
@@ -27,7 +29,7 @@ def wrapped(self, request, *args, **kwargs):
"sudoRequired": True,
"username": request.user.username,
}
- return HttpResponse(json.dumps(data), status=401)
+ return Response(data, status=401)
return func(self, request, *args, **kwargs)
return wrapped
diff --git a/src/sentry/api/endpoints/auth_index.py b/src/sentry/api/endpoints/auth_index.py
index 7634f6c47ff194..c7e4af156c7ef7 100644
--- a/src/sentry/api/endpoints/auth_index.py
+++ b/src/sentry/api/endpoints/auth_index.py
@@ -5,9 +5,10 @@
from rest_framework.response import Response
from sentry.api.authentication import QuietBasicAuthentication
-from sentry.models import Authenticator
from sentry.api.base import Endpoint
from sentry.api.serializers import serialize
+from sentry.auth.superuser import is_active_superuser
+from sentry.models import Authenticator
from sentry.utils import auth
@@ -33,7 +34,7 @@ def get(self, request):
return Response(status=400)
data = serialize(request.user, request.user)
- data['isSuperuser'] = request.is_superuser()
+ data['isSuperuser'] = is_active_superuser(request)
return Response(data)
def post(self, request):
diff --git a/src/sentry/api/endpoints/authenticator_index.py b/src/sentry/api/endpoints/authenticator_index.py
new file mode 100644
index 00000000000000..3cc2b468edcb46
--- /dev/null
+++ b/src/sentry/api/endpoints/authenticator_index.py
@@ -0,0 +1,33 @@
+from __future__ import absolute_import
+
+from rest_framework.response import Response
+from rest_framework.permissions import IsAuthenticated
+
+from sentry.api.base import Endpoint
+from sentry.models import Authenticator
+
+
+class AuthenticatorIndexEndpoint(Endpoint):
+ permission_classes = (IsAuthenticated, )
+
+ def get(self, request):
+ """Returns u2f interface for a user, otherwise an empty array
+ """
+
+ # Currently just expose u2f challenge, not sure if it's necessary to list all
+ # authenticator interfaces that are enabled
+ try:
+ interface = Authenticator.objects.get_interface(request.user, 'u2f')
+ if not interface.is_enrolled:
+ raise LookupError()
+ except LookupError:
+ return Response([])
+
+ challenge = interface.activate(request._request).challenge
+
+ # I don't think we currently support multiple interfaces of the same type
+ # but just future proofing I guess
+ return Response([{
+ 'id': 'u2f',
+ 'challenge': challenge
+ }])
diff --git a/src/sentry/api/endpoints/chunk_upload.py b/src/sentry/api/endpoints/chunk_upload.py
new file mode 100644
index 00000000000000..895c5bfd5f8888
--- /dev/null
+++ b/src/sentry/api/endpoints/chunk_upload.py
@@ -0,0 +1,64 @@
+from __future__ import absolute_import
+
+# from rest_framework.permissions import IsAuthenticated
+from rest_framework import status
+from rest_framework.response import Response
+
+from sentry import options
+from sentry.models import FileBlob
+from sentry.models.file import DEFAULT_BLOB_SIZE
+from sentry.api.base import Endpoint
+from sentry.api.bases.project import ProjectReleasePermission
+
+
+MAX_CHUNKS_PER_REQUEST = 16
+MAX_CONCURRENCY = 4
+HASH_ALGORITHM = 'sha1'
+
+
+class ChunkUploadEndpoint(Endpoint):
+ permission_classes = (ProjectReleasePermission, )
+
+ def get(self, request):
+ endpoint = options.get('system.upload-url-prefix')
+ # We fallback to default system url if config is not set
+ if len(endpoint) == 0:
+ endpoint = options.get('system.url-prefix')
+
+ return Response(
+ {
+ 'url': endpoint,
+ 'chunkSize': DEFAULT_BLOB_SIZE,
+ 'chunksPerRequest': MAX_CHUNKS_PER_REQUEST,
+ 'concurrency': MAX_CONCURRENCY,
+ 'hashAlgorithm': HASH_ALGORITHM,
+ }
+ )
+
+ def post(self, request):
+ files = request.FILES.getlist('file')
+
+ if len(files) > MAX_CHUNKS_PER_REQUEST:
+ return Response({'error': 'Too many chunks'},
+ status=status.HTTP_400_BAD_REQUEST)
+ elif len(files) == 0:
+ # No files uploaded is ok
+ return Response(status=status.HTTP_200_OK)
+
+ # Validate file size
+ checksum_list = []
+ for chunk in files:
+ if chunk._size > DEFAULT_BLOB_SIZE:
+ return Response({'error': 'Chunk size too large'},
+ status=status.HTTP_400_BAD_REQUEST)
+ checksum_list.append(chunk._name)
+
+ for chunk in files:
+ # Here we create the actual file
+ blob = FileBlob.from_file(chunk)
+ if blob.checksum not in checksum_list:
+ # We do not clean up here since we have a cleanup job
+ return Response({'error': 'Checksum missmatch'},
+ status=status.HTTP_400_BAD_REQUEST)
+
+ return Response(status=status.HTTP_200_OK)
diff --git a/src/sentry/api/endpoints/dsym_files.py b/src/sentry/api/endpoints/dsym_files.py
index c97c20d5d2657d..d4d96893b5348b 100644
--- a/src/sentry/api/endpoints/dsym_files.py
+++ b/src/sentry/api/endpoints/dsym_files.py
@@ -36,7 +36,7 @@ class AssociateDsymSerializer(serializers.Serializer):
build = serializers.CharField(max_length=40, required=False)
-def upload_from_request(request, project=None):
+def upload_from_request(request, project):
if 'file' not in request.FILES:
return Response({'detail': 'Missing uploaded file'}, status=400)
fileobj = request.FILES['file']
diff --git a/src/sentry/api/endpoints/event_apple_crash_report.py b/src/sentry/api/endpoints/event_apple_crash_report.py
index eb6db5a4466fbb..6dee47a0587103 100644
--- a/src/sentry/api/endpoints/event_apple_crash_report.py
+++ b/src/sentry/api/endpoints/event_apple_crash_report.py
@@ -38,7 +38,7 @@ def get(self, request, event_id):
Event.objects.bind_nodes([event], 'data')
- if event.platform != 'cocoa':
+ if event.platform not in ('cocoa', 'native'):
return HttpResponse(
{
'message': 'Only cocoa events can return an apple crash report',
diff --git a/src/sentry/api/endpoints/event_file_committers.py b/src/sentry/api/endpoints/event_file_committers.py
index 36b34d4d4ab7a2..dee24095dad189 100644
--- a/src/sentry/api/endpoints/event_file_committers.py
+++ b/src/sentry/api/endpoints/event_file_committers.py
@@ -14,6 +14,7 @@
from itertools import izip
from collections import defaultdict
+from six.moves import reduce
def tokenize_path(path):
@@ -126,7 +127,7 @@ def _get_committers(self, annotated_frames, commits):
def get(self, _, project, event_id):
"""
Retrieve Committer information for an event
- ```````````````````````````````
+ ```````````````````````````````````````````
Return commiters on an individual event, plus a per-frame breakdown.
@@ -165,8 +166,8 @@ def get(self, _, project, event_id):
return Response({'detail': 'No Commits found for Release'}, status=404)
frames = self._get_frame_paths(event)
- frame_limit = 15
- app_frames = [frame for frame in frames if frame['in_app']][:frame_limit]
+ frame_limit = 25
+ app_frames = [frame for frame in frames if frame['in_app']][-frame_limit:]
# TODO(maxbittker) return this set instead of annotated frames
path_set = {frame['abs_path'] for frame in app_frames}
diff --git a/src/sentry/api/endpoints/filechange.py b/src/sentry/api/endpoints/filechange.py
index ad71e2e8fa41da..d802beedf9cfdb 100644
--- a/src/sentry/api/endpoints/filechange.py
+++ b/src/sentry/api/endpoints/filechange.py
@@ -16,7 +16,7 @@ class CommitFileChangeEndpoint(OrganizationReleasesBaseEndpoint):
def get(self, request, organization, version):
"""
Retrieve Files Changed in a Release's Commits
- ````````````````````````
+ `````````````````````````````````````````````
Retrieve a list of files that were changed in a given release's commits.
diff --git a/src/sentry/api/endpoints/group_details.py b/src/sentry/api/endpoints/group_details.py
index b2bc1d5345acb2..ad654d873e5a59 100644
--- a/src/sentry/api/endpoints/group_details.py
+++ b/src/sentry/api/endpoints/group_details.py
@@ -1,25 +1,26 @@
from __future__ import absolute_import
from datetime import timedelta
+import functools
import logging
from uuid import uuid4
from django.utils import timezone
from rest_framework.response import Response
-from sentry import tsdb
+from sentry import tsdb, tagstore
from sentry.api import client
-from sentry.api.base import DocSection
+from sentry.api.base import DocSection, EnvironmentMixin
from sentry.api.bases import GroupEndpoint
-from sentry.api.serializers import serialize
+from sentry.api.serializers import serialize, GroupSerializer
from sentry.api.serializers.models.plugin import PluginSerializer
from sentry.models import (
Activity,
+ Environment,
Group,
GroupHash,
GroupSeen,
GroupStatus,
- GroupTagKey,
Release,
User,
UserReport,
@@ -67,7 +68,7 @@ def delete_aggregate_scenario(runner):
}
-class GroupDetailsEndpoint(GroupEndpoint):
+class GroupDetailsEndpoint(GroupEndpoint, EnvironmentMixin):
doc_section = DocSection.EVENTS
def _get_activity(self, request, group, num):
@@ -173,7 +174,14 @@ def get(self, request, group):
:auth: required
"""
# TODO(dcramer): handle unauthenticated/public response
- data = serialize(group, request.user)
+ data = serialize(
+ group,
+ request.user,
+ GroupSerializer(
+ environment_func=self._get_environment_func(
+ request, group.project.organization_id)
+ )
+ )
# TODO: these probably should be another endpoint
activity = self._get_activity(request, group, num=100)
@@ -188,9 +196,26 @@ def get(self, request, group):
action_list = self._get_actions(request, group)
+ if first_release:
+ first_release = self._get_release_info(request, group, first_release)
+ if last_release:
+ last_release = self._get_release_info(request, group, last_release)
+
+ try:
+ environment_id = self._get_environment_id_from_request(
+ request, group.project.organization_id)
+ except Environment.DoesNotExist:
+ get_range = lambda model, keys, start, end, **kwargs: \
+ {k: tsdb.make_series(0, start, end) for k in keys}
+ tags = []
+ else:
+ get_range = functools.partial(tsdb.get_range, environment_id=environment_id)
+ tags = tagstore.get_group_tag_keys(
+ group.project_id, group.id, environment_id, limit=100)
+
now = timezone.now()
hourly_stats = tsdb.rollup(
- tsdb.get_range(
+ get_range(
model=tsdb.models.group,
keys=[group.id],
end=now,
@@ -198,7 +223,7 @@ def get(self, request, group):
), 3600
)[group.id]
daily_stats = tsdb.rollup(
- tsdb.get_range(
+ get_range(
model=tsdb.models.group,
keys=[group.id],
end=now,
@@ -206,15 +231,6 @@ def get(self, request, group):
), 3600 * 24
)[group.id]
- if first_release:
- first_release = self._get_release_info(request, group, first_release)
- if last_release:
- last_release = self._get_release_info(request, group, last_release)
-
- tags = list(GroupTagKey.objects.filter(
- group_id=group.id,
- )[:100])
-
participants = list(
User.objects.filter(
groupsubscription__is_active=True,
@@ -266,6 +282,7 @@ def put(self, request, group):
user context this allows changing of
the bookmark flag.
:param boolean isSubscribed:
+ :param boolean isPublic: sets the issue to public or private.
:auth: required
"""
discard = request.DATA.get('discard')
@@ -296,7 +313,16 @@ def put(self, request, group):
# for mutation.
group = Group.objects.get(id=group.id)
- return Response(serialize(group, request.user), status=response.status_code)
+ serialized = serialize(
+ group,
+ request.user,
+ GroupSerializer(
+ environment_func=self._get_environment_func(
+ request, group.project.organization_id)
+ )
+ )
+
+ return Response(serialized, status=response.status_code)
@attach_scenarios([delete_aggregate_scenario])
def delete(self, request, group):
diff --git a/src/sentry/api/endpoints/group_events.py b/src/sentry/api/endpoints/group_events.py
index 9f484f3b6f4cee..7b53aa9deb4a48 100644
--- a/src/sentry/api/endpoints/group_events.py
+++ b/src/sentry/api/endpoints/group_events.py
@@ -3,15 +3,16 @@
import six
from sentry import tagstore
-from sentry.api.base import DocSection
+from sentry.api.base import DocSection, EnvironmentMixin
from sentry.api.bases import GroupEndpoint
from sentry.api.serializers import serialize
from sentry.api.paginator import DateTimePaginator
-from sentry.models import Event, Group
+from sentry.models import Environment, Event, Group
from sentry.search.utils import parse_query
from sentry.utils.apidocs import scenario, attach_scenarios
from rest_framework.response import Response
from sentry.search.utils import InvalidQuery
+from django.db.models import Q
@scenario('ListAvailableSamples')
@@ -20,7 +21,7 @@ def list_available_samples_scenario(runner):
runner.request(method='GET', path='/issues/%s/events/' % group.id)
-class GroupEventsEndpoint(GroupEndpoint):
+class GroupEventsEndpoint(GroupEndpoint, EnvironmentMixin):
doc_section = DocSection.EVENTS
@attach_scenarios([list_available_samples_scenario])
@@ -40,6 +41,7 @@ def get(self, request, group):
)
query = request.GET.get('query')
+
if query:
try:
query_kwargs = parse_query(group.project, query, request.user)
@@ -47,13 +49,23 @@ def get(self, request, group):
return Response({'detail': six.text_type(exc)}, status=400)
if query_kwargs['query']:
- events = events.filter(
- message__icontains=query_kwargs['query'],
- )
+ q = Q(message__icontains=query_kwargs['query'])
+
+ if len(query) == 32:
+ q |= Q(event_id__exact=query_kwargs['query'])
+
+ events = events.filter(q)
if query_kwargs['tags']:
- event_ids = tagstore.get_group_event_ids(
- group.project_id, group.id, query_kwargs['tags'])
+ try:
+ environment_id = self._get_environment_id_from_request(
+ request, group.project.organization_id)
+ except Environment.DoesNotExist:
+ event_ids = []
+ else:
+ event_ids = tagstore.get_group_event_ids(
+ group.project_id, group.id, environment_id, query_kwargs['tags'])
+
if event_ids:
events = events.filter(
id__in=event_ids,
diff --git a/src/sentry/api/endpoints/group_similar_issues.py b/src/sentry/api/endpoints/group_similar_issues.py
index b6ba2de80a5c73..2f711ff1983ee2 100644
--- a/src/sentry/api/endpoints/group_similar_issues.py
+++ b/src/sentry/api/endpoints/group_similar_issues.py
@@ -18,7 +18,7 @@ def get(self, request, group):
limit = int(limit) + 1 # the target group will always be included
results = filter(
- lambda (group_id, scores): group_id != group.id,
+ lambda group_id__scores: group_id__scores[0] != group.id,
features.compare(group, limit=limit)
)
@@ -32,9 +32,9 @@ def get(self, request, group):
# unexpected behavior, but still possible.)
return Response(
filter(
- lambda (group_id, scores): group_id is not None,
+ lambda group_id__scores: group_id__scores[0] is not None,
map(
- lambda (group_id, scores): (serialized_groups.get(group_id), scores, ),
+ lambda group_id__scores: (serialized_groups.get(group_id__scores[0]), group_id__scores[1], ),
results,
),
),
diff --git a/src/sentry/api/endpoints/group_stats.py b/src/sentry/api/endpoints/group_stats.py
index 6c9d67bd73680b..acf1297fa86fea 100644
--- a/src/sentry/api/endpoints/group_stats.py
+++ b/src/sentry/api/endpoints/group_stats.py
@@ -3,14 +3,27 @@
from rest_framework.response import Response
from sentry import tsdb
-from sentry.api.base import StatsMixin
+from sentry.api.base import EnvironmentMixin, StatsMixin
from sentry.api.bases.group import GroupEndpoint
+from sentry.api.exceptions import ResourceDoesNotExist
+from sentry.models import Environment
-class GroupStatsEndpoint(GroupEndpoint, StatsMixin):
+class GroupStatsEndpoint(GroupEndpoint, EnvironmentMixin, StatsMixin):
def get(self, request, group):
+ try:
+ environment_id = self._get_environment_id_from_request(
+ request,
+ group.project.organization_id,
+ )
+ except Environment.DoesNotExist:
+ raise ResourceDoesNotExist
+
data = tsdb.get_range(
- model=tsdb.models.group, keys=[group.id], **self._parse_args(request)
+ model=tsdb.models.group, keys=[group.id], **self._parse_args(
+ request,
+ environment_id,
+ )
)[group.id]
return Response(data)
diff --git a/src/sentry/api/endpoints/group_tagkey_details.py b/src/sentry/api/endpoints/group_tagkey_details.py
index b78bcab8d3d8bf..187e8ff83a6bc7 100644
--- a/src/sentry/api/endpoints/group_tagkey_details.py
+++ b/src/sentry/api/endpoints/group_tagkey_details.py
@@ -5,11 +5,11 @@
from rest_framework.response import Response
from sentry import tagstore
-from sentry.api.base import DocSection
+from sentry.api.base import DocSection, EnvironmentMixin
from sentry.api.bases.group import GroupEndpoint
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.api.serializers import serialize
-from sentry.models import (GroupTagKey, GroupTagValue, Group)
+from sentry.models import Environment, Group
from sentry.utils.apidocs import scenario
@@ -22,7 +22,7 @@ def list_tag_details_scenario(runner):
)
-class GroupTagKeyDetailsEndpoint(GroupEndpoint):
+class GroupTagKeyDetailsEndpoint(GroupEndpoint, EnvironmentMixin):
doc_section = DocSection.EVENTS
# XXX: this scenario does not work for some inexplicable reasons
@@ -41,26 +41,32 @@ def get(self, request, group, key):
lookup_key = tagstore.prefix_reserved_key(key)
try:
- tag_key = tagstore.get_tag_key(group.project_id, lookup_key)
- except tagstore.TagKeyNotFound:
+ environment_id = self._get_environment_id_from_request(
+ request, group.project.organization_id)
+ except Environment.DoesNotExist:
+ # if the environment doesn't exist then the tag can't possibly exist
raise ResourceDoesNotExist
try:
- group_tag_key = GroupTagKey.objects.get(
- group_id=group.id,
- key=lookup_key,
- )
- except GroupTagKey.DoesNotExist:
+ tag_key = tagstore.get_tag_key(group.project_id, environment_id, lookup_key)
+ except tagstore.TagKeyNotFound:
raise ResourceDoesNotExist
- total_values = GroupTagValue.get_value_count(group.id, lookup_key)
+ try:
+ group_tag_key = tagstore.get_group_tag_key(
+ group.project_id, group.id, environment_id, lookup_key)
+ except tagstore.GroupTagKeyNotFound:
+ raise ResourceDoesNotExist
- top_values = GroupTagValue.get_top_values(group.id, lookup_key, limit=9)
+ total_values = tagstore.get_group_tag_value_count(
+ group.project_id, group.id, environment_id, lookup_key)
+ top_values = tagstore.get_top_group_tag_values(
+ group.project_id, group.id, environment_id, lookup_key, limit=9)
data = {
'id': six.text_type(tag_key.id),
'key': key,
- 'name': tag_key.get_label(),
+ 'name': tagstore.get_tag_key_label(tag_key.key),
'uniqueValues': group_tag_key.values_seen,
'totalValues': total_values,
'topValues': serialize(top_values, request.user),
diff --git a/src/sentry/api/endpoints/group_tagkey_values.py b/src/sentry/api/endpoints/group_tagkey_values.py
index 3ad6a12b35d091..bbfca41842a39c 100644
--- a/src/sentry/api/endpoints/group_tagkey_values.py
+++ b/src/sentry/api/endpoints/group_tagkey_values.py
@@ -1,13 +1,13 @@
from __future__ import absolute_import
from sentry import tagstore
-from sentry.api.base import DocSection
+from sentry.api.base import DocSection, EnvironmentMixin
from sentry.api.bases.group import GroupEndpoint
from sentry.api.exceptions import ResourceDoesNotExist
-from sentry.api.paginator import DateTimePaginator, OffsetPaginator, Paginator
+from sentry.api.paginator import DateTimePaginator, Paginator
from sentry.api.serializers import serialize
from sentry.api.serializers.models.tagvalue import UserTagValueSerializer
-from sentry.models import GroupTagValue, Group
+from sentry.models import Group, Environment
from sentry.utils.apidocs import scenario
@@ -20,7 +20,7 @@ def list_tag_values_scenario(runner):
)
-class GroupTagKeyValuesEndpoint(GroupEndpoint):
+class GroupTagKeyValuesEndpoint(GroupEndpoint, EnvironmentMixin):
doc_section = DocSection.EVENTS
# XXX: this scenario does not work for some inexplicable reasons
@@ -39,14 +39,19 @@ def get(self, request, group, key):
lookup_key = tagstore.prefix_reserved_key(key)
try:
- tagstore.get_tag_key(group.project_id, lookup_key)
+ environment_id = self._get_environment_id_from_request(
+ request, group.project.organization_id)
+ except Environment.DoesNotExist:
+ # if the environment doesn't exist then the tag can't possibly exist
+ raise ResourceDoesNotExist
+
+ try:
+ tagstore.get_tag_key(group.project_id, environment_id, lookup_key)
except tagstore.TagKeyNotFound:
raise ResourceDoesNotExist
- queryset = GroupTagValue.objects.filter(
- group_id=group.id,
- key=lookup_key,
- )
+ queryset = tagstore.get_group_tag_value_qs(
+ group.project_id, group.id, environment_id, lookup_key)
sort = request.GET.get('sort')
if sort == 'date':
@@ -55,9 +60,6 @@ def get(self, request, group, key):
elif sort == 'age':
order_by = '-first_seen'
paginator_cls = DateTimePaginator
- elif sort == 'freq':
- order_by = '-times_seen'
- paginator_cls = OffsetPaginator
else:
order_by = '-id'
paginator_cls = Paginator
diff --git a/src/sentry/api/endpoints/group_tags.py b/src/sentry/api/endpoints/group_tags.py
index d8606c0641faa3..7577ef5aaf3838 100644
--- a/src/sentry/api/endpoints/group_tags.py
+++ b/src/sentry/api/endpoints/group_tags.py
@@ -2,38 +2,43 @@
import six
+from collections import defaultdict
from rest_framework.response import Response
-from collections import defaultdict
from sentry import tagstore
+from sentry.api.base import EnvironmentMixin
from sentry.api.bases.group import GroupEndpoint
from sentry.api.serializers import serialize
-from sentry.models import GroupTagValue, GroupTagKey
+from sentry.models import Environment
-class GroupTagsEndpoint(GroupEndpoint):
+class GroupTagsEndpoint(GroupEndpoint, EnvironmentMixin):
def get(self, request, group):
- grouptagkeys = list(GroupTagKey.objects.filter(
- group_id=group.id
- ).values_list('key', flat=True))
-
- tag_keys = tagstore.get_tag_keys(group.project_id, grouptagkeys)
+ try:
+ environment_id = self._get_environment_id_from_request(
+ request, group.project.organization_id)
+ except Environment.DoesNotExist:
+ group_tag_keys = []
+ else:
+ group_tag_keys = tagstore.get_group_tag_keys(group.project_id, group.id, environment_id)
# O(N) db access
data = []
all_top_values = []
- for tag_key in tag_keys:
- total_values = GroupTagValue.get_value_count(group.id, tag_key.key)
- top_values = GroupTagValue.get_top_values(group.id, tag_key.key, limit=10)
+ for group_tag_key in group_tag_keys:
+ total_values = tagstore.get_group_tag_value_count(
+ group.project_id, group.id, environment_id, group_tag_key.key)
+ top_values = tagstore.get_top_group_tag_values(
+ group.project_id, group.id, environment_id, group_tag_key.key, limit=10)
all_top_values.extend(top_values)
data.append(
{
- 'id': six.text_type(tag_key.id),
- 'key': tagstore.get_standardized_key(tag_key.key),
- 'name': tag_key.get_label(),
- 'uniqueValues': tag_key.values_seen,
+ 'id': six.text_type(group_tag_key.id),
+ 'key': tagstore.get_standardized_key(group_tag_key.key),
+ 'name': tagstore.get_tag_key_label(group_tag_key.key),
+ 'uniqueValues': group_tag_key.values_seen,
'totalValues': total_values,
}
)
diff --git a/src/sentry/api/endpoints/group_tombstone.py b/src/sentry/api/endpoints/group_tombstone.py
index e7a36c74b51ef2..36d797416a7a93 100644
--- a/src/sentry/api/endpoints/group_tombstone.py
+++ b/src/sentry/api/endpoints/group_tombstone.py
@@ -11,7 +11,7 @@ class GroupTombstoneEndpoint(ProjectEndpoint):
def get(self, request, project):
"""
Retrieve a Project's GroupTombstones
- ```````````````
+ ````````````````````````````````````
Lists a project's `GroupTombstone` objects
diff --git a/src/sentry/api/endpoints/group_tombstone_details.py b/src/sentry/api/endpoints/group_tombstone_details.py
index 5c6d364bfbc242..116bcf2b80799a 100644
--- a/src/sentry/api/endpoints/group_tombstone_details.py
+++ b/src/sentry/api/endpoints/group_tombstone_details.py
@@ -15,7 +15,7 @@ class GroupTombstoneDetailsEndpoint(ProjectEndpoint):
def delete(self, request, project, tombstone_id):
"""
Remove a GroupTombstone
- ```````````````
+ ```````````````````````
Undiscards a group such that new events in that group will be captured.
This does not restore any previous data.
diff --git a/src/sentry/api/endpoints/internal_quotas.py b/src/sentry/api/endpoints/internal_quotas.py
new file mode 100644
index 00000000000000..12d11b82d92a01
--- /dev/null
+++ b/src/sentry/api/endpoints/internal_quotas.py
@@ -0,0 +1,22 @@
+from __future__ import absolute_import
+
+from django.conf import settings
+from rest_framework.response import Response
+
+from sentry import options
+from sentry.api.base import Endpoint
+from sentry.api.permissions import SuperuserPermission
+
+
+class InternalQuotasEndpoint(Endpoint):
+ permission_classes = (SuperuserPermission, )
+
+ def get(self, request):
+ return Response(
+ {
+ 'backend': settings.SENTRY_QUOTAS,
+ 'options': {
+ 'system.rate-limit': options.get('system.rate-limit'),
+ }
+ }
+ )
diff --git a/src/sentry/api/endpoints/issues_resolved_in_release.py b/src/sentry/api/endpoints/issues_resolved_in_release.py
index 33beac817b14c5..86f610ba4ca802 100644
--- a/src/sentry/api/endpoints/issues_resolved_in_release.py
+++ b/src/sentry/api/endpoints/issues_resolved_in_release.py
@@ -2,28 +2,28 @@
from rest_framework.response import Response
-from sentry.api.base import DocSection
+from sentry.api.base import DocSection, EnvironmentMixin
from sentry.api.bases.project import ProjectEndpoint, ProjectPermission
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.api.serializers import serialize
-from sentry.api.serializers.models.group import StreamGroupSerializer
+from sentry.api.serializers.models.group import GroupSerializer
from sentry.models import (
Group,
- GroupCommitResolution,
+ GroupLink,
GroupResolution,
Release,
ReleaseCommit,
)
-class IssuesResolvedInReleaseEndpoint(ProjectEndpoint):
+class IssuesResolvedInReleaseEndpoint(ProjectEndpoint, EnvironmentMixin):
doc_section = DocSection.RELEASES
permission_classes = (ProjectPermission, )
def get(self, request, project, version):
"""
List issues to be resolved in a particular release
- ````````````````````````
+ ``````````````````````````````````````````````````
Retrieve a list of issues to be resolved in a given release.
@@ -45,8 +45,9 @@ def get(self, request, project, version):
).values_list('group_id', flat=True)
)
group_ids |= set(
- GroupCommitResolution.objects.filter(
- commit_id__in=ReleaseCommit.objects.filter(
+ GroupLink.objects.filter(
+ linked_type=GroupLink.LinkedType.commit,
+ linked_id__in=ReleaseCommit.objects.filter(
release=release,
).values_list(
'commit_id',
@@ -60,5 +61,12 @@ def get(self, request, project, version):
groups = Group.objects.filter(project=project, id__in=group_ids)
- context = serialize(list(groups), request.user, StreamGroupSerializer(stats_period=None))
+ context = serialize(
+ list(groups),
+ request.user,
+ GroupSerializer(
+ environment_func=self._get_environment_func(request, project.organization_id)
+ )
+ )
+
return Response(context)
diff --git a/src/sentry/api/endpoints/legacy_project_redirect.py b/src/sentry/api/endpoints/legacy_project_redirect.py
deleted file mode 100644
index 3f0ad57c8df6b3..00000000000000
--- a/src/sentry/api/endpoints/legacy_project_redirect.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from __future__ import absolute_import
-
-from django.http import HttpResponseRedirect
-
-from sentry.api.base import Endpoint
-from sentry.api.bases.project import ProjectPermission
-from sentry.api.exceptions import ResourceDoesNotExist
-from sentry.models import Project
-from sentry.utils.http import absolute_uri
-
-
-class LegacyProjectRedirectEndpoint(Endpoint):
- permission_classes = (ProjectPermission, )
-
- def convert_args(self, request, project_id, *args, **kwargs):
- try:
- project = Project.objects.get_from_cache(
- id=project_id,
- )
- except Project.DoesNotExist:
- raise ResourceDoesNotExist
-
- self.check_object_permissions(request, project)
-
- kwargs['project'] = project
- return (args, kwargs)
-
- def get(self, request, project, path):
- """
- Retrieve a project
-
- Return details on an individual project.
-
- {method} {path}
-
- """
- return HttpResponseRedirect(
- absolute_uri(
- '/api/0/projects/{}/{}/{}'.format(
- project.organization.slug,
- project.slug,
- path or '',
- )
- )
- )
diff --git a/src/sentry/api/endpoints/organization_access_request_details.py b/src/sentry/api/endpoints/organization_access_request_details.py
index 4bbc896a2d636e..45a5400fdf20b9 100644
--- a/src/sentry/api/endpoints/organization_access_request_details.py
+++ b/src/sentry/api/endpoints/organization_access_request_details.py
@@ -6,12 +6,23 @@
from sentry.api.bases.organization import (OrganizationEndpoint, OrganizationPermission)
from sentry.api.exceptions import ResourceDoesNotExist
+from sentry.api.serializers import serialize
from sentry.models import (AuditLogEntryEvent, OrganizationAccessRequest, OrganizationMemberTeam)
class AccessRequestPermission(OrganizationPermission):
scope_map = {
- 'GET': [],
+ 'GET': [
+ 'org:read',
+ 'org:write',
+ 'org:admin',
+ 'team:read',
+ 'team:write',
+ 'team:admin',
+ 'member:read',
+ 'member:write',
+ 'member:admin',
+ ],
'POST': [],
'PUT': [
'org:write',
@@ -48,6 +59,31 @@ def _can_access(self, request, access_request):
return True
return False
+ def get(self, request, organization):
+ """
+ Get list of requests to join org/team
+
+ """
+ if request.access.has_scope('org:write'):
+ access_requests = list(
+ OrganizationAccessRequest.objects.filter(
+ team__organization=organization,
+ member__user__is_active=True,
+ ).select_related('team', 'member__user')
+ )
+ elif request.access.has_scope('team:write') and request.access.teams:
+ access_requests = list(
+ OrganizationAccessRequest.objects.filter(
+ member__user__is_active=True,
+ team__in=request.access.teams,
+ ).select_related('team', 'member__user')
+ )
+ else:
+ # Return empty response if user does not have access
+ return Response([])
+
+ return Response(serialize(access_requests, request.user))
+
def put(self, request, organization, request_id):
"""
Approve or deny a request
diff --git a/src/sentry/api/endpoints/organization_activity.py b/src/sentry/api/endpoints/organization_activity.py
index fc169e96b177da..500a96b8e6904f 100644
--- a/src/sentry/api/endpoints/organization_activity.py
+++ b/src/sentry/api/endpoints/organization_activity.py
@@ -1,17 +1,18 @@
from __future__ import absolute_import
+from sentry.api.base import EnvironmentMixin
from sentry.api.bases import OrganizationMemberEndpoint
from sentry.api.paginator import DateTimePaginator
from sentry.api.serializers import serialize, OrganizationActivitySerializer
from sentry.models import Activity, OrganizationMemberTeam, Project
-class OrganizationActivityEndpoint(OrganizationMemberEndpoint):
+class OrganizationActivityEndpoint(OrganizationMemberEndpoint, EnvironmentMixin):
def get(self, request, organization, member):
queryset = Activity.objects.filter(
project__in=Project.objects.filter(
organization=organization,
- team__in=OrganizationMemberTeam.objects.filter(
+ teams__in=OrganizationMemberTeam.objects.filter(
organizationmember=member,
).values('team')
)
@@ -27,5 +28,8 @@ def get(self, request, organization, member):
queryset=queryset,
paginator_cls=DateTimePaginator,
order_by='-datetime',
- on_results=lambda x: serialize(x, request.user, OrganizationActivitySerializer()),
+ on_results=lambda x: serialize(x, request.user, OrganizationActivitySerializer(
+ environment_func=self._get_environment_func(
+ request, organization.id)
+ )),
)
diff --git a/src/sentry/api/endpoints/organization_api_key_details.py b/src/sentry/api/endpoints/organization_api_key_details.py
new file mode 100644
index 00000000000000..440ba3d39c722f
--- /dev/null
+++ b/src/sentry/api/endpoints/organization_api_key_details.py
@@ -0,0 +1,110 @@
+from __future__ import absolute_import
+
+from rest_framework import serializers, status
+from rest_framework.response import Response
+
+from sentry.api.bases.organization import OrganizationEndpoint, OrganizationAdminPermission
+from sentry.api.exceptions import ResourceDoesNotExist
+from sentry.api.serializers import serialize
+from sentry.models import ApiKey, AuditLogEntryEvent
+
+
+class ApiKeySerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ApiKey
+ fields = ('label', 'scope_list', 'allowed_origins')
+
+
+class OrganizationApiKeyDetailsEndpoint(OrganizationEndpoint):
+ permission_classes = (OrganizationAdminPermission, )
+
+ def get(self, request, organization, api_key_id):
+ """
+ Retrieves API Key details
+ `````````````````````````
+
+ :pparam string organization_slug: the slug of the organization the
+ team belongs to.
+ :pparam string api_key_id: the ID of the api key to delete
+ :auth: required
+ """
+ try:
+ api_key = ApiKey.objects.get(
+ id=api_key_id,
+ organization_id=organization.id,
+ )
+ except ApiKey.DoesNotExist:
+ raise ResourceDoesNotExist
+
+ return Response(serialize(api_key, request.user))
+
+ def put(self, request, organization, api_key_id):
+ """
+ Update an API Key
+ `````````````````
+
+ :pparam string organization_slug: the slug of the organization the
+ team belongs to.
+ :pparam string api_key_id: the ID of the api key to delete
+ :param string label: the new label for the api key
+ :param array scope_list: an array of scopes available for api key
+ :param string allowed_origins: list of allowed origins
+ :auth: required
+ """
+
+ try:
+ api_key = ApiKey.objects.get(
+ id=api_key_id,
+ organization_id=organization.id,
+ )
+ except ApiKey.DoesNotExist:
+ raise ResourceDoesNotExist
+
+ serializer = ApiKeySerializer(api_key, data=request.DATA, partial=True)
+
+ if serializer.is_valid():
+ api_key = serializer.save()
+
+ self.create_audit_entry(
+ request=request,
+ organization=organization,
+ target_object=api_key_id,
+ event=AuditLogEntryEvent.APIKEY_EDIT,
+ data=api_key.get_audit_log_data(),
+ )
+
+ return Response(serialize(api_key, request.user))
+
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ def delete(self, request, organization, api_key_id):
+ """
+ Deletes an API Key
+ ``````````````````
+
+ :pparam string organization_slug: the slug of the organization the
+ team belongs to.
+ :pparam string api_key_id: the ID of the api key to delete
+ :auth: required
+ """
+ try:
+ api_key = ApiKey.objects.get(
+ id=api_key_id,
+ organization_id=organization.id,
+ )
+ except ApiKey.DoesNotExist:
+ raise ResourceDoesNotExist
+
+ audit_data = api_key.get_audit_log_data()
+
+ api_key.delete()
+
+ self.create_audit_entry(
+ request,
+ organization=organization,
+ target_object=api_key.id,
+ event=AuditLogEntryEvent.APIKEY_REMOVE,
+ data=audit_data,
+ )
+
+ return Response(status=status.HTTP_204_NO_CONTENT)
diff --git a/src/sentry/api/endpoints/organization_api_key_index.py b/src/sentry/api/endpoints/organization_api_key_index.py
new file mode 100644
index 00000000000000..419b4f68a4b487
--- /dev/null
+++ b/src/sentry/api/endpoints/organization_api_key_index.py
@@ -0,0 +1,59 @@
+from __future__ import absolute_import
+
+from rest_framework import status
+from rest_framework.response import Response
+
+from sentry.api.bases.organization import OrganizationEndpoint, OrganizationAdminPermission
+from sentry.api.serializers import serialize
+from sentry.models import ApiKey, AuditLogEntryEvent
+
+DEFAULT_SCOPES = [
+ 'project:read',
+ 'event:read',
+ 'team:read',
+ 'org:read',
+ 'member:read',
+]
+
+
+class OrganizationApiKeyIndexEndpoint(OrganizationEndpoint):
+ permission_classes = (OrganizationAdminPermission, )
+
+ def get(self, request, organization):
+ """
+ List an Organization's API Keys
+ ```````````````````````````````````
+
+ :pparam string organization_slug: the organization short name
+ :auth: required
+ """
+ queryset = sorted(
+ ApiKey.objects.filter(
+ organization=organization,
+ ), key=lambda x: x.label
+ )
+
+ return Response(serialize(queryset, request.user))
+
+ def post(self, request, organization):
+ """
+ Create an Organization API Key
+ ```````````````````````````````````
+
+ :pparam string organization_slug: the organization short name
+ :auth: required
+ """
+ key = ApiKey.objects.create(
+ organization=organization,
+ scope_list=DEFAULT_SCOPES,
+ )
+
+ self.create_audit_entry(
+ request,
+ organization=organization,
+ target_object=key.id,
+ event=AuditLogEntryEvent.APIKEY_ADD,
+ data=key.get_audit_log_data(),
+ )
+
+ return Response(serialize(key, request.user), status=status.HTTP_201_CREATED)
diff --git a/src/sentry/api/endpoints/organization_auth_provider_details.py b/src/sentry/api/endpoints/organization_auth_provider_details.py
new file mode 100644
index 00000000000000..496a711c6cfaf8
--- /dev/null
+++ b/src/sentry/api/endpoints/organization_auth_provider_details.py
@@ -0,0 +1,42 @@
+from __future__ import absolute_import
+
+from django.utils.translation import ugettext_lazy as _
+from rest_framework import status
+from rest_framework.response import Response
+
+from sentry import features
+from sentry.api.bases.organization import OrganizationEndpoint, OrganizationAuthProviderPermission
+from sentry.api.serializers import serialize
+from sentry.models import AuthProvider
+
+ERR_NO_SSO = _('The SSO feature is not enabled for this organization.')
+
+
+class OrganizationAuthProviderDetailsEndpoint(OrganizationEndpoint):
+ permission_classes = (OrganizationAuthProviderPermission, )
+
+ def get(self, request, organization):
+ """
+ Retrieve details about Organization's SSO settings and
+ currently installed auth_provider
+ ``````````````````````````````````````````````````````
+
+ :pparam string organization_slug: the organization short name
+ :auth: required
+ """
+ if not features.has('organizations:sso', organization, actor=request.user):
+ return Response(ERR_NO_SSO, status=status.HTTP_403_FORBIDDEN)
+
+ try:
+ auth_provider = AuthProvider.objects.get(
+ organization=organization,
+ )
+ except AuthProvider.DoesNotExist:
+ # This is a valid state where org does not have an auth provider
+ # configured, make sure we respond with a 20x
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+ # cache organization so that we don't need to query for org when serializing
+ auth_provider._organization_cache = organization
+
+ return Response(serialize(auth_provider, request.user))
diff --git a/src/sentry/api/endpoints/organization_auth_provider_send_reminders.py b/src/sentry/api/endpoints/organization_auth_provider_send_reminders.py
new file mode 100644
index 00000000000000..fcb3b5c64d8112
--- /dev/null
+++ b/src/sentry/api/endpoints/organization_auth_provider_send_reminders.py
@@ -0,0 +1,31 @@
+from __future__ import absolute_import
+
+from django.utils.translation import ugettext_lazy as _
+from rest_framework.response import Response
+
+from sentry import features
+from sentry.api.bases.organization import OrganizationEndpoint, OrganizationAdminPermission
+from sentry.api.exceptions import ResourceDoesNotExist
+from sentry.models import AuthProvider
+from sentry.tasks.auth import email_missing_links
+
+ERR_NO_SSO = _('The SSO feature is not enabled for this organization.')
+
+
+class OrganizationAuthProviderSendRemindersEndpoint(OrganizationEndpoint):
+ permission_classes = (OrganizationAdminPermission, )
+
+ def post(self, request, organization):
+ if not features.has('organizations:sso', organization, actor=request.user):
+ return Response(ERR_NO_SSO, status=403)
+
+ try:
+ auth_provider = AuthProvider.objects.get(
+ organization=organization,
+ )
+ except AuthProvider.DoesNotExist:
+ raise ResourceDoesNotExist
+
+ email_missing_links.delay(
+ organization.id, request.user.id, auth_provider.key)
+ return Response(status=200)
diff --git a/src/sentry/api/endpoints/organization_auth_providers.py b/src/sentry/api/endpoints/organization_auth_providers.py
new file mode 100644
index 00000000000000..0e3dd383e99327
--- /dev/null
+++ b/src/sentry/api/endpoints/organization_auth_providers.py
@@ -0,0 +1,34 @@
+from __future__ import absolute_import
+
+from rest_framework.response import Response
+
+from sentry import features
+from sentry.auth import manager
+from sentry.auth.providers.saml2 import SAML2Provider, HAS_SAML2
+from sentry.api.bases.organization import OrganizationEndpoint, OrganizationAdminPermission
+from sentry.api.serializers import serialize
+
+
+class OrganizationAuthProvidersEndpoint(OrganizationEndpoint):
+ permission_classes = (OrganizationAdminPermission, )
+
+ def get(self, request, organization):
+ """
+ List available auth providers that are available to use for an Organization
+ ```````````````````````````````````````````````````````````````````````````
+
+ :pparam string organization_slug: the organization short name
+ :auth: required
+ """
+ provider_list = []
+ for k, v in manager:
+ if issubclass(v, SAML2Provider) and not HAS_SAML2:
+ continue
+
+ feature = v.required_feature
+ if feature and not features.has(feature, organization, actor=request.user):
+ continue
+
+ provider_list.append((k, v.name))
+
+ return Response(serialize(provider_list, request.user))
diff --git a/src/sentry/api/endpoints/organization_config_integrations.py b/src/sentry/api/endpoints/organization_config_integrations.py
index 73e799536336e8..053aabd5d2d7fd 100644
--- a/src/sentry/api/endpoints/organization_config_integrations.py
+++ b/src/sentry/api/endpoints/organization_config_integrations.py
@@ -12,13 +12,13 @@ def get(self, request, organization):
for provider in integrations.all():
providers.append(
{
- 'id': provider.id,
+ 'key': provider.key,
'name': provider.name,
'config': provider.get_config(),
'setupDialog': dict(
url='/organizations/{}/integrations/{}/setup/'.format(
organization.slug,
- provider.id,
+ provider.key,
),
**provider.setup_dialog_config
)
diff --git a/src/sentry/api/endpoints/organization_details.py b/src/sentry/api/endpoints/organization_details.py
index 722d77ee92dc5a..71e00050b292b2 100644
--- a/src/sentry/api/endpoints/organization_details.py
+++ b/src/sentry/api/endpoints/organization_details.py
@@ -1,6 +1,7 @@
from __future__ import absolute_import
import logging
+import six
from rest_framework import serializers, status
from rest_framework.response import Response
@@ -12,10 +13,12 @@
from sentry.api.decorators import sudo_required
from sentry.api.fields import AvatarField
from sentry.api.serializers import serialize
-from sentry.api.serializers.models.organization import (DetailedOrganizationSerializer)
+from sentry.api.serializers.models.organization import (
+ DetailedOrganizationSerializer)
from sentry.api.serializers.rest_framework import ListField
+from sentry.constants import RESERVED_ORGANIZATION_SLUGS
from sentry.models import (
- AuditLogEntryEvent, Organization, OrganizationAvatar, OrganizationOption, OrganizationStatus
+ AuditLogEntryEvent, Authenticator, Organization, OrganizationAvatar, OrganizationOption, OrganizationStatus
)
from sentry.tasks.deletion import delete_organization
from sentry.utils.apidocs import scenario, attach_scenarios
@@ -35,6 +38,9 @@
delete_logger = logging.getLogger('sentry.deletions.api')
+DELETION_STATUSES = frozenset([OrganizationStatus.PENDING_DELETION,
+ OrganizationStatus.DELETION_IN_PROGRESS])
+
@scenario('RetrieveOrganization')
def retrieve_organization_scenario(runner):
@@ -59,8 +65,10 @@ def update_organization_scenario(runner):
class OrganizationSerializer(serializers.Serializer):
name = serializers.CharField(max_length=64)
slug = serializers.RegexField(r'^[a-z0-9_\-]+$', max_length=50)
- accountRateLimit = serializers.IntegerField(min_value=0, max_value=1000000, required=False)
- projectRateLimit = serializers.IntegerField(min_value=50, max_value=100, required=False)
+ accountRateLimit = serializers.IntegerField(
+ min_value=0, max_value=1000000, required=False)
+ projectRateLimit = serializers.IntegerField(
+ min_value=50, max_value=100, required=False)
avatar = AvatarField(required=False)
avatarType = serializers.ChoiceField(
choices=(('upload', 'upload'), ('letter_avatar', 'letter_avatar'), ), required=False
@@ -75,10 +83,26 @@ class OrganizationSerializer(serializers.Serializer):
safeFields = ListField(child=serializers.CharField(), required=False)
scrubIPAddresses = serializers.BooleanField(required=False)
isEarlyAdopter = serializers.BooleanField(required=False)
+ require2FA = serializers.BooleanField(required=False)
def validate_slug(self, attrs, source):
value = attrs[source]
- if Organization.objects.filter(slug=value).exclude(id=self.context['organization'].id):
+ # Historically, the only check just made sure there was more than 1
+ # character for the slug, but since then, there are many slugs that
+ # fit within this new imposed limit. We're not fixing existing, but
+ # just preventing new bad values.
+ if len(value) < 3:
+ raise serializers.ValidationError(
+ 'This slug "%s" is too short. Minimum of 3 characters.' %
+ (value, ))
+ if value in RESERVED_ORGANIZATION_SLUGS:
+ raise serializers.ValidationError(
+ 'This slug "%s" is reserved and not allowed.' %
+ (value, ))
+ qs = Organization.objects.filter(
+ slug=value,
+ ).exclude(id=self.context['organization'].id)
+ if qs.exists():
raise serializers.ValidationError('The slug "%s" is already in use.' % (value, ))
return attrs
@@ -94,6 +118,15 @@ def validate_safeFields(self, attrs, source):
raise serializers.ValidationError('Empty values are not allowed.')
return attrs
+ def validate_require2FA(self, attrs, source):
+ value = attrs[source]
+ user = self.context['user']
+ has_2fa = Authenticator.objects.user_has_2fa(user)
+ if value and not has_2fa:
+ raise serializers.ValidationError(
+ 'User setting two-factor authentication enforcement without two-factor authentication enabled.')
+ return attrs
+
def validate(self, attrs):
attrs = super(OrganizationSerializer, self).validate(attrs)
if attrs.get('avatarType') == 'upload':
@@ -111,6 +144,29 @@ def validate(self, attrs):
def save(self):
org = self.context['organization']
+ changed_data = {}
+
+ for key, option, type_ in ORG_OPTIONS:
+ if key not in self.init_data:
+ continue
+ try:
+ option_inst = OrganizationOption.objects.get(
+ organization=org, key=option)
+ except OrganizationOption.DoesNotExist:
+ OrganizationOption.objects.set_value(
+ organization=org,
+ key=option,
+ value=type_(self.init_data[key]),
+ )
+ changed_data[key] = self.init_data[key]
+ else:
+ option_inst.value = self.init_data[key]
+ # check if ORG_OPTIONS changed
+ if option_inst.has_changed('value'):
+ old_val = option_inst.old_value('value')
+ changed_data[key] = u'from {} to {}'.format(old_val, option_inst.value)
+ option_inst.save()
+
if 'openMembership' in self.init_data:
org.flags.allow_joinleave = self.init_data['openMembership']
if 'allowSharedIssues' in self.init_data:
@@ -119,18 +175,39 @@ def save(self):
org.flags.enhanced_privacy = self.init_data['enhancedPrivacy']
if 'isEarlyAdopter' in self.init_data:
org.flags.early_adopter = self.init_data['isEarlyAdopter']
+ if 'require2FA' in self.init_data:
+ org.flags.require_2fa = self.init_data['require2FA']
if 'name' in self.init_data:
org.name = self.init_data['name']
if 'slug' in self.init_data:
org.slug = self.init_data['slug']
+
+ org_tracked_field = {
+ 'name': org.name,
+ 'slug': org.slug,
+ 'default_role': org.default_role,
+ 'flag_field': {
+ 'allow_joinleave': org.flags.allow_joinleave.is_set,
+ 'enhanced_privacy': org.flags.enhanced_privacy.is_set,
+ 'disable_shared_issues': org.flags.disable_shared_issues.is_set,
+ 'early_adopter': org.flags.early_adopter.is_set
+ }
+ }
+
+ # check if fields changed
+ for f, v in six.iteritems(org_tracked_field):
+ if f is not 'flag_field':
+ if org.has_changed(f):
+ old_val = org.old_value(f)
+ changed_data[f] = u'from {} to {}'.format(old_val, v)
+ else:
+ # check if flag fields changed
+ for f, v in six.iteritems(org_tracked_field['flag_field']):
+ if org.flag_has_changed(f):
+ changed_data[f] = u'to {}'.format(v)
+
org.save()
- for key, option, type_ in ORG_OPTIONS:
- if key in self.init_data:
- OrganizationOption.objects.set_value(
- organization=org,
- key=option,
- value=type_(self.init_data[key]),
- )
+
if 'avatar' in self.init_data or 'avatarType' in self.init_data:
OrganizationAvatar.save_avatar(
relation={'organization': org},
@@ -138,16 +215,22 @@ def save(self):
avatar=self.init_data.get('avatar'),
filename='{}.png'.format(org.slug),
)
- return org
+ if 'require2FA' in self.init_data and self.init_data['require2FA'] is True:
+ org.send_setup_2fa_emails()
+ return org, changed_data
class OwnerOrganizationSerializer(OrganizationSerializer):
defaultRole = serializers.ChoiceField(choices=roles.get_choices())
+ cancelDeletion = serializers.BooleanField(required=False)
def save(self, *args, **kwargs):
org = self.context['organization']
+ cancel_deletion = 'cancelDeletion' in self.init_data and org.status in DELETION_STATUSES
if 'defaultRole' in self.init_data:
org.default_role = self.init_data['defaultRole']
+ if cancel_deletion:
+ org.status = OrganizationStatus.VISIBLE
return super(OwnerOrganizationSerializer, self).save(*args, **kwargs)
@@ -194,20 +277,39 @@ def put(self, request, organization):
serializer_cls = OwnerOrganizationSerializer
else:
serializer_cls = OrganizationSerializer
+
+ was_pending_deletion = organization.status in DELETION_STATUSES
+
serializer = serializer_cls(
data=request.DATA,
partial=True,
- context={'organization': organization},
+ context={'organization': organization, 'user': request.user},
)
if serializer.is_valid():
- organization = serializer.save()
+ organization, changed_data = serializer.save()
+
+ if was_pending_deletion and organization.status == OrganizationStatus.VISIBLE:
+ self.create_audit_entry(
+ request=request,
+ organization=organization,
+ target_object=organization.id,
+ event=AuditLogEntryEvent.ORG_RESTORE,
+ data=organization.get_audit_log_data(),
+ )
+ delete_logger.info(
+ 'object.delete.canceled',
+ extra={
+ 'object_id': organization.id,
+ 'model': Organization.__name__,
+ }
+ )
self.create_audit_entry(
request=request,
organization=organization,
target_object=organization.id,
event=AuditLogEntryEvent.ORG_EDIT,
- data=organization.get_audit_log_data(),
+ data=changed_data
)
return Response(
@@ -217,7 +319,6 @@ def put(self, request, organization):
DetailedOrganizationSerializer(),
)
)
-
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@sudo_required
diff --git a/src/sentry/api/endpoints/organization_index.py b/src/sentry/api/endpoints/organization_index.py
index 267bf7b36e33e1..fe1e62912748fb 100644
--- a/src/sentry/api/endpoints/organization_index.py
+++ b/src/sentry/api/endpoints/organization_index.py
@@ -13,6 +13,7 @@
from sentry.api.bases.organization import OrganizationPermission
from sentry.api.paginator import DateTimePaginator, OffsetPaginator
from sentry.api.serializers import serialize
+from sentry.auth.superuser import is_active_superuser
from sentry.db.models.query import in_iexact
from sentry.models import (
AuditLogEntryEvent, Organization, OrganizationMember, OrganizationMemberTeam,
@@ -50,21 +51,39 @@ def get(self, request):
:qparam bool member: restrict results to organizations which you have
membership
+ :qparam bool owner: restrict results to organizations which are owner
:auth: required
"""
member_only = request.GET.get('member') in ('1', 'true')
+ owner_only = request.GET.get('owner') in ('1', 'true')
- queryset = Organization.objects.filter(
- status=OrganizationStatus.VISIBLE,
- )
+ queryset = Organization.objects.all()
if request.auth and not request.user.is_authenticated():
if hasattr(request.auth, 'project'):
queryset = queryset.filter(id=request.auth.project.organization_id)
elif request.auth.organization is not None:
queryset = queryset.filter(id=request.auth.organization.id)
- elif member_only or not request.is_superuser():
+
+ elif owner_only:
+ # This is used when closing an account
+ queryset = queryset.filter(
+ member_set__role=roles.get_top_dog().id,
+ member_set__user=request.user,
+ status=OrganizationStatus.VISIBLE,
+ )
+ org_results = []
+ for org in sorted(queryset, key=lambda x: x.name):
+ # O(N) query
+ org_results.append({
+ 'organization': serialize(org),
+ 'singleOwner': org.has_single_owner(),
+ })
+
+ return Response(org_results)
+
+ elif member_only or not is_active_superuser(request):
queryset = queryset.filter(
id__in=OrganizationMember.objects.filter(
user=request.user,
@@ -93,6 +112,15 @@ def get(self, request):
)
elif key == 'id':
queryset = queryset.filter(id__in=value)
+ elif key == 'status':
+ try:
+ queryset = queryset.filter(status__in=[
+ OrganizationStatus[v.upper()] for v in value
+ ])
+ except KeyError:
+ queryset = queryset.none()
+ else:
+ queryset = queryset.none()
sort_by = request.GET.get('sortBy')
if sort_by == 'members':
diff --git a/src/sentry/api/endpoints/organization_integrations.py b/src/sentry/api/endpoints/organization_integrations.py
index 87a1c07cf05022..5cdfd29f502f3a 100644
--- a/src/sentry/api/endpoints/organization_integrations.py
+++ b/src/sentry/api/endpoints/organization_integrations.py
@@ -21,7 +21,10 @@ def has_feature(self, request, organization):
def get(self, request, organization):
if not self.has_feature(request, organization):
- return self.respond({'detail': ['You do not have that feature enabled']}, status=400)
+ return self.respond({
+ 'error_type': 'unavailable_feature',
+ 'detail': ['You do not have that feature enabled']
+ }, status=403)
return self.paginate(
queryset=Integration.objects.filter(organizations=organization),
diff --git a/src/sentry/api/endpoints/organization_member_details.py b/src/sentry/api/endpoints/organization_member_details.py
index 27240a4a60f6bf..6a284abcd37f0c 100644
--- a/src/sentry/api/endpoints/organization_member_details.py
+++ b/src/sentry/api/endpoints/organization_member_details.py
@@ -6,9 +6,14 @@
from rest_framework.response import Response
from sentry import roles
-from sentry.api.bases.organization import (OrganizationEndpoint, OrganizationPermission)
+from sentry.api.bases.organization import (
+ OrganizationEndpoint, OrganizationPermission)
from sentry.api.exceptions import ResourceDoesNotExist
-from sentry.models import (AuditLogEntryEvent, AuthIdentity, AuthProvider, OrganizationMember)
+from sentry.api.serializers import serialize, RoleSerializer, OrganizationMemberWithTeamsSerializer
+from sentry.api.serializers.rest_framework import ListField
+from sentry.auth.superuser import is_active_superuser
+from sentry.models import (
+ AuditLogEntryEvent, AuthIdentity, AuthProvider, OrganizationMember, OrganizationMemberTeam, Team, TeamStatus)
from sentry.signals import sso_enabled
ERR_NO_AUTH = 'You cannot remove this member with an unauthenticated API request.'
@@ -22,8 +27,33 @@
ERR_UNINVITABLE = 'You cannot send an invitation to a user who is already a full member.'
+def get_allowed_roles(request, organization, member=None):
+ can_admin = request.access.has_scope('member:admin')
+
+ allowed_roles = []
+ if can_admin and not is_active_superuser(request):
+ acting_member = member or OrganizationMember.objects.get(
+ user=request.user,
+ organization=organization,
+ )
+ if member and roles.get(acting_member.role).priority < roles.get(member.role).priority:
+ can_admin = False
+ else:
+ allowed_roles = [
+ r for r in roles.get_all()
+ if r.priority <= roles.get(acting_member.role).priority
+ ]
+ can_admin = bool(allowed_roles)
+ elif is_active_superuser(request):
+ allowed_roles = roles.get_all()
+ return (can_admin, allowed_roles, )
+
+
class OrganizationMemberSerializer(serializers.Serializer):
reinvite = serializers.BooleanField()
+ regenerate = serializers.BooleanField()
+ role = serializers.ChoiceField(choices=roles.get_choices(), required=True)
+ teams = ListField(required=False, allow_null=False)
class RelaxedMemberPermission(OrganizationPermission):
@@ -71,35 +101,114 @@ def _is_only_owner(self, member):
return True
+ def _serialize_member(self, member, request, allowed_roles=None):
+ context = serialize(
+ member,
+ serializer=OrganizationMemberWithTeamsSerializer()
+ )
+
+ if request.access.has_scope('member:admin'):
+ context['invite_link'] = member.get_invite_link()
+
+ context['isOnlyOwner'] = self._is_only_owner(member)
+ context['roles'] = serialize(
+ roles.get_all(), serializer=RoleSerializer(), allowed_roles=allowed_roles)
+
+ return context
+
+ def get(self, request, organization, member_id):
+ """Currently only returns allowed invite roles for member invite"""
+
+ try:
+ member = self._get_member(request, organization, member_id)
+ except OrganizationMember.DoesNotExist:
+ raise ResourceDoesNotExist
+
+ _, allowed_roles = get_allowed_roles(request, organization, member)
+
+ context = self._serialize_member(member, request, allowed_roles)
+
+ return Response(context)
+
def put(self, request, organization, member_id):
try:
om = self._get_member(request, organization, member_id)
except OrganizationMember.DoesNotExist:
raise ResourceDoesNotExist
- serializer = OrganizationMemberSerializer(data=request.DATA, partial=True)
+ # You can't edit your own membership
+ if om.user == request.user:
+ return Response(
+ {'detail': 'You cannot make changes to your own membership.'}, status=400)
+
+ serializer = OrganizationMemberSerializer(
+ data=request.DATA, partial=True)
+
if not serializer.is_valid():
return Response(status=400)
- has_sso = AuthProvider.objects.filter(
- organization=organization,
- ).exists()
+ try:
+ auth_provider = AuthProvider.objects.get(organization=organization)
+ auth_provider = auth_provider.get_provider()
+ except AuthProvider.DoesNotExist:
+ auth_provider = None
+ allowed_roles = None
result = serializer.object
+
# XXX(dcramer): if/when this expands beyond reinvite we need to check
# access level
if result.get('reinvite'):
if om.is_pending:
+ if result.get('regenerate'):
+ if request.access.has_scope('member:admin'):
+ om.update(token=om.generate_token())
+ else:
+ return Response({'detail': ERR_INSUFFICIENT_SCOPE}, status=400)
+
om.send_invite_email()
- elif has_sso and not getattr(om.flags, 'sso:linked'):
- om.send_sso_link_email()
+ elif auth_provider and not getattr(om.flags, 'sso:linked'):
+ om.send_sso_link_email(request.user, auth_provider)
else:
# TODO(dcramer): proper error message
return Response({'detail': ERR_UNINVITABLE}, status=400)
- if has_sso:
+ if auth_provider:
sso_enabled.send(organization=organization, sender=request.user)
- return Response(status=204)
+ if result.get('teams'):
+ # dupe code from member_index
+ # ensure listed teams are real teams
+ teams = list(Team.objects.filter(
+ organization=organization,
+ status=TeamStatus.VISIBLE,
+ slug__in=result['teams'],
+ ))
+
+ if len(set(result['teams'])) != len(teams):
+ return Response({'teams': 'Invalid team'}, status=400)
+
+ with transaction.atomic():
+ # teams may be empty
+ OrganizationMemberTeam.objects.filter(
+ organizationmember=om).delete()
+ OrganizationMemberTeam.objects.bulk_create(
+ [
+ OrganizationMemberTeam(
+ team=team, organizationmember=om)
+ for team in teams
+ ]
+ )
+
+ if result.get('role'):
+ _, allowed_roles = get_allowed_roles(request, organization)
+ if not result['role'] in {r.id for r in allowed_roles}:
+ return Response(
+ {'role': 'You do not have permission to invite that role.'}, status=403)
+ om.update(role=result['role'])
+
+ context = self._serialize_member(om, request, allowed_roles)
+
+ return Response(context)
def delete(self, request, organization, member_id):
try:
@@ -107,7 +216,7 @@ def delete(self, request, organization, member_id):
except OrganizationMember.DoesNotExist:
raise ResourceDoesNotExist
- if request.user.is_authenticated() and not request.is_superuser():
+ if request.user.is_authenticated() and not is_active_superuser(request):
try:
acting_member = OrganizationMember.objects.get(
organization=organization,
diff --git a/src/sentry/api/endpoints/organization_member_index.py b/src/sentry/api/endpoints/organization_member_index.py
index 5b1c4165d912b9..cc7513c412e4d7 100644
--- a/src/sentry/api/endpoints/organization_member_index.py
+++ b/src/sentry/api/endpoints/organization_member_index.py
@@ -1,13 +1,24 @@
from __future__ import absolute_import
import six
+from django.db import transaction, IntegrityError
from django.db.models import Q
+from rest_framework import serializers
+from rest_framework.response import Response
+from django.conf import settings
-from sentry.api.bases.organization import (OrganizationEndpoint, OrganizationPermission)
+from sentry.app import locks
+from sentry import roles, features
+from sentry.api.bases.organization import (
+ OrganizationEndpoint, OrganizationPermission)
from sentry.api.paginator import OffsetPaginator
from sentry.api.serializers import serialize
-from sentry.models import OrganizationMember
+from sentry.api.serializers.rest_framework import ListField
+from sentry.models import AuditLogEntryEvent, OrganizationMember, OrganizationMemberTeam, Team, TeamStatus
from sentry.search.utils import tokenize_query
+from sentry.signals import member_invited
+from .organization_member_details import get_allowed_roles
+from sentry.utils.retries import TimedRetryPolicy
class MemberPermission(OrganizationPermission):
@@ -19,9 +30,28 @@ class MemberPermission(OrganizationPermission):
}
+class OrganizationMemberSerializer(serializers.Serializer):
+ email = serializers.EmailField(max_length=75, required=True)
+ role = serializers.ChoiceField(choices=roles.get_choices(), required=True)
+ teams = ListField(required=False, allow_null=False)
+
+
class OrganizationMemberIndexEndpoint(OrganizationEndpoint):
permission_classes = (MemberPermission, )
+ @transaction.atomic
+ def save_team_assignments(self, organization_member, teams):
+ # teams may be empty
+ OrganizationMemberTeam.objects.filter(
+ organizationmember=organization_member).delete()
+ OrganizationMemberTeam.objects.bulk_create(
+ [
+ OrganizationMemberTeam(
+ team=team, organizationmember=organization_member)
+ for team in teams
+ ]
+ )
+
def get(self, request, organization):
queryset = OrganizationMember.objects.filter(
Q(user__is_active=True) | Q(user__isnull=True),
@@ -34,7 +64,8 @@ def get(self, request, organization):
for key, value in six.iteritems(tokens):
if key == 'email':
queryset = queryset.filter(
- Q(user__email__in=value) | Q(user__emails__email__in=value)
+ Q(user__email__in=value) | Q(
+ user__emails__email__in=value)
)
return self.paginate(
@@ -43,3 +74,90 @@ def get(self, request, organization):
on_results=lambda x: serialize(x, request.user),
paginator_cls=OffsetPaginator,
)
+
+ def post(self, request, organization):
+ """
+ Add a Member to Organization
+ ````````````````````````````
+
+ Invite a member to the organization.
+
+ :pparam string organization_slug: the slug of the organization the member will belong to
+ :param string email: the email address to invite
+ :param string role: the role of the new member
+ :param array teams: the slugs of the teams the member should belong to.
+
+ :auth: required
+ """
+ # TODO: If the member already exists, should this still update the role and team?
+ # For now, it doesn't, but simply returns the existing object
+
+ if not features.has('organizations:invite-members', organization, actor=request.user):
+ return Response(
+ {'organization': 'Your organization is not allowed to invite members'}, status=403)
+
+ serializer = OrganizationMemberSerializer(data=request.DATA)
+
+ if not serializer.is_valid():
+ return Response(serializer.errors, status=400)
+
+ result = serializer.object
+
+ _, allowed_roles = get_allowed_roles(request, organization)
+
+ # ensure listed teams are real teams
+ teams = list(Team.objects.filter(
+ organization=organization,
+ status=TeamStatus.VISIBLE,
+ slug__in=result['teams'],
+ ))
+
+ if len(set(result['teams'])) != len(teams):
+ return Response({'teams': 'Invalid team'}, 400)
+
+ if not result['role'] in {r.id for r in allowed_roles}:
+ return Response({'role': 'You do not have permission to invite that role.'}, 403)
+
+ # This is needed because `email` field is case sensitive, but from a user perspective,
+ # Sentry treats email as case-insensitive (Eric@example.com equals eric@example.com).
+
+ existing = OrganizationMember.objects.filter(
+ organization=organization,
+ user__email__iexact=result['email'],
+ user__is_active=True,
+ ).exists()
+
+ if existing:
+ return Response({'email': 'The user %s is already a member' % result['email']}, 409)
+
+ om = OrganizationMember(
+ organization=organization,
+ email=result['email'],
+ role=result['role'])
+
+ if settings.SENTRY_ENABLE_INVITES:
+ om.token = om.generate_token()
+
+ try:
+ with transaction.atomic():
+ om.save()
+ except IntegrityError:
+ return Response({'email': 'The user %s is already a member' % result['email']}, 409)
+
+ lock = locks.get('org:member:{}'.format(om.id), duration=5)
+ with TimedRetryPolicy(10)(lock.acquire):
+ self.save_team_assignments(om, teams)
+
+ if settings.SENTRY_ENABLE_INVITES:
+ om.send_invite_email()
+ member_invited.send(member=om, user=request.user, sender=self)
+
+ self.create_audit_entry(
+ request=request,
+ organization_id=organization.id,
+ target_object=om.id,
+ data=om.get_audit_log_data(),
+ event=AuditLogEntryEvent.MEMBER_INVITE if settings.SENTRY_ENABLE_INVITES else AuditLogEntryEvent.MEMBER_ADD,
+ )
+
+ return Response(serialize(om), status=201)
diff --git a/src/sentry/api/endpoints/organization_member_team_details.py b/src/sentry/api/endpoints/organization_member_team_details.py
index b4c09c303c5a1e..f424c016b73e8b 100644
--- a/src/sentry/api/endpoints/organization_member_team_details.py
+++ b/src/sentry/api/endpoints/organization_member_team_details.py
@@ -8,6 +8,7 @@
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.api.serializers import serialize
from sentry.api.serializers.models.team import TeamWithProjectsSerializer
+from sentry.auth.superuser import is_active_superuser
from sentry.models import (
AuditLogEntryEvent, OrganizationAccessRequest, OrganizationMember, OrganizationMemberTeam, Team
)
@@ -45,7 +46,7 @@ class OrganizationMemberTeamDetailsEndpoint(OrganizationEndpoint):
def _can_access(self, request, member):
# TODO(dcramer): ideally org owners/admins could perform these actions
- if request.is_superuser():
+ if is_active_superuser(request):
return True
if not request.user.is_authenticated():
diff --git a/src/sentry/api/endpoints/organization_projects.py b/src/sentry/api/endpoints/organization_projects.py
index 31ddacfe3e56fd..661e87a21b5f05 100644
--- a/src/sentry/api/endpoints/organization_projects.py
+++ b/src/sentry/api/endpoints/organization_projects.py
@@ -54,18 +54,18 @@ def get(self, request, organization):
if request.auth and not request.user.is_authenticated():
# TODO: remove this, no longer supported probably
if hasattr(request.auth, 'project'):
- team_list = [request.auth.project.team]
+ team_list = list(request.auth.project.teams.all())
queryset = queryset = Project.objects.filter(
id=request.auth.project.id,
- ).select_related('team')
+ ).prefetch_related('teams')
elif request.auth.organization is not None:
org = request.auth.organization
team_list = list(Team.objects.filter(
organization=org,
))
queryset = Project.objects.filter(
- team__in=team_list,
- ).select_related('team')
+ teams__in=team_list,
+ ).prefetch_related('teams')
else:
return Response(
{
@@ -76,8 +76,8 @@ def get(self, request, organization):
else:
team_list = list(request.access.teams)
queryset = Project.objects.filter(
- team__in=team_list,
- ).select_related('team')
+ teams__in=team_list,
+ ).prefetch_related('team')
return self.paginate(
request=request,
diff --git a/src/sentry/api/endpoints/organization_release_details.py b/src/sentry/api/endpoints/organization_release_details.py
index fb7c8d5eabb46a..0eeea2554b073f 100644
--- a/src/sentry/api/endpoints/organization_release_details.py
+++ b/src/sentry/api/endpoints/organization_release_details.py
@@ -16,6 +16,7 @@
)
from sentry.models import Activity, Group, Release, ReleaseFile
from sentry.utils.apidocs import scenario, attach_scenarios
+from sentry.constants import VERSION_LENGTH
ERR_RELEASE_REFERENCED = "This release is referenced by active issues and cannot be removed."
@@ -42,7 +43,7 @@ def update_organization_release_scenario(runner):
class ReleaseSerializer(serializers.Serializer):
- ref = serializers.CharField(max_length=64, required=False)
+ ref = serializers.CharField(max_length=VERSION_LENGTH, required=False)
url = serializers.URLField(required=False)
dateReleased = serializers.DateTimeField(required=False)
commits = ListField(child=CommitSerializer(), required=False, allow_null=False)
@@ -181,7 +182,7 @@ def put(self, request, organization, version):
Activity.objects.create(
type=Activity.RELEASE,
project=project,
- ident=release.version,
+ ident=Activity.get_version_ident(release.version),
data={'version': release.version},
datetime=release.date_released,
)
diff --git a/src/sentry/api/endpoints/organization_releases.py b/src/sentry/api/endpoints/organization_releases.py
index 1f667131bfd9d5..372058a8c005e3 100644
--- a/src/sentry/api/endpoints/organization_releases.py
+++ b/src/sentry/api/endpoints/organization_releases.py
@@ -170,7 +170,7 @@ def post(self, request, organization):
Activity.objects.create(
type=Activity.RELEASE,
project=project,
- ident=result['version'],
+ ident=Activity.get_version_ident(result['version']),
data={'version': result['version']},
datetime=release.date_released,
)
diff --git a/src/sentry/api/endpoints/organization_repositories.py b/src/sentry/api/endpoints/organization_repositories.py
index f9900d87baf7aa..f66c153ac5af8d 100644
--- a/src/sentry/api/endpoints/organization_repositories.py
+++ b/src/sentry/api/endpoints/organization_repositories.py
@@ -2,6 +2,7 @@
from rest_framework.response import Response
+from sentry import features
from sentry.api.base import DocSection
from sentry.api.bases.organization import OrganizationEndpoint
from sentry.api.paginator import OffsetPaginator
@@ -14,6 +15,13 @@
class OrganizationRepositoriesEndpoint(OrganizationEndpoint):
doc_section = DocSection.ORGANIZATIONS
+ def has_feature(self, request, organization):
+ return features.has(
+ 'organizations:repos',
+ organization=organization,
+ actor=request.user,
+ )
+
def get(self, request, organization):
"""
List an Organization's Repositories
@@ -24,6 +32,12 @@ def get(self, request, organization):
:pparam string organization_slug: the organization short name
:auth: required
"""
+ if not self.has_feature(request, organization):
+ return self.respond({
+ 'error_type': 'unavailable_feature',
+ 'detail': ['You do not have that feature enabled']
+ }, status=403)
+
queryset = Repository.objects.filter(
organization_id=organization.id,
)
@@ -52,6 +66,12 @@ def post(self, request, organization):
if not request.user.is_authenticated():
return Response(status=401)
+ if not self.has_feature(request, organization):
+ return self.respond({
+ 'error_type': 'unavailable_feature',
+ 'detail': ['You do not have that feature enabled']
+ }, status=403)
+
provider_id = request.DATA.get('provider')
try:
provider_cls = bindings.get('repository.provider').get(provider_id)
diff --git a/src/sentry/api/endpoints/organization_repository_details.py b/src/sentry/api/endpoints/organization_repository_details.py
index c4a812247031ee..031e58448ac465 100644
--- a/src/sentry/api/endpoints/organization_repository_details.py
+++ b/src/sentry/api/endpoints/organization_repository_details.py
@@ -22,7 +22,11 @@ def get_transaction_id():
class RepositorySerializer(serializers.Serializer):
- status = serializers.ChoiceField(choices=(('visible', 'visible'), ))
+ status = serializers.ChoiceField(choices=(
+ # XXX(dcramer): these are aliased, and we prefer 'active' over 'visible'
+ ('visible', 'visible'),
+ ('active', 'active'),
+ ))
class OrganizationRepositoryDetailsEndpoint(OrganizationEndpoint):
@@ -48,7 +52,7 @@ def put(self, request, organization, repo_id):
if serializer.is_valid():
result = serializer.object
if result.get('status'):
- if result['status'] == 'visible':
+ if result['status'] in ('visible', 'active'):
repo.update(status=ObjectStatus.VISIBLE)
else:
raise NotImplementedError
diff --git a/src/sentry/api/endpoints/organization_stats.py b/src/sentry/api/endpoints/organization_stats.py
index f14263c46e353f..70e969f39c7650 100644
--- a/src/sentry/api/endpoints/organization_stats.py
+++ b/src/sentry/api/endpoints/organization_stats.py
@@ -3,9 +3,10 @@
from rest_framework.response import Response
from sentry import tsdb
-from sentry.api.base import DocSection, StatsMixin
+from sentry.api.base import DocSection, EnvironmentMixin, StatsMixin
from sentry.api.bases.organization import OrganizationEndpoint
-from sentry.models import Project, Team
+from sentry.api.exceptions import ResourceDoesNotExist
+from sentry.models import Environment, Project, Team
from sentry.utils.apidocs import attach_scenarios, scenario
@@ -14,7 +15,7 @@ def retrieve_event_counts_organization(runner):
runner.request(method='GET', path='/organizations/%s/stats/' % runner.org.slug)
-class OrganizationStatsEndpoint(OrganizationEndpoint, StatsMixin):
+class OrganizationStatsEndpoint(OrganizationEndpoint, EnvironmentMixin, StatsMixin):
doc_section = DocSection.ORGANIZATIONS
@attach_scenarios([retrieve_event_counts_organization])
@@ -60,7 +61,7 @@ def get(self, request, organization):
team=team,
user=request.user,
))
- keys = [p.id for p in project_list]
+ keys = list({p.id for p in project_list})
else:
raise ValueError('Invalid group: %s' % group)
@@ -73,6 +74,7 @@ def get(self, request, organization):
stat_model = None
stat = request.GET.get('stat', 'received')
+ query_kwargs = {}
if stat == 'received':
if group == 'project':
stat_model = tsdb.models.project_total_received
@@ -91,11 +93,19 @@ def get(self, request, organization):
elif stat == 'generated':
if group == 'project':
stat_model = tsdb.models.project
+ try:
+ query_kwargs['environment_id'] = self._get_environment_id_from_request(
+ request,
+ organization.id,
+ )
+ except Environment.DoesNotExist:
+ raise ResourceDoesNotExist
if stat_model is None:
raise ValueError('Invalid group: %s, stat: %s' % (group, stat))
- data = tsdb.get_range(model=stat_model, keys=keys, **self._parse_args(request))
+ data = tsdb.get_range(model=stat_model, keys=keys,
+ **self._parse_args(request, **query_kwargs))
if group == 'organization':
data = data[organization.id]
diff --git a/src/sentry/api/endpoints/organization_user_issues.py b/src/sentry/api/endpoints/organization_user_issues.py
index a32adacfed5995..4d46fa9e649928 100644
--- a/src/sentry/api/endpoints/organization_user_issues.py
+++ b/src/sentry/api/endpoints/organization_user_issues.py
@@ -1,17 +1,16 @@
from __future__ import absolute_import
-from django.db.models import Q
-from operator import or_
from rest_framework.response import Response
-from six.moves import reduce
+from sentry import tagstore
+from sentry.api.base import EnvironmentMixin
from sentry.api.bases.organization import OrganizationEndpoint
from sentry.api.serializers import serialize
from sentry.api.serializers.models.group import TagBasedStreamGroupSerializer
-from sentry.models import (EventUser, Group, GroupTagValue, Project)
+from sentry.models import (EventUser, Group, ProjectTeam, Team)
-class OrganizationUserIssuesEndpoint(OrganizationEndpoint):
+class OrganizationUserIssuesEndpoint(OrganizationEndpoint, EnvironmentMixin):
def get(self, request, organization, user_id):
limit = request.GET.get('limit', 100)
@@ -22,22 +21,24 @@ def get(self, request, organization, user_id):
)
# they have organization access but not to this project, thus
# they shouldn't be able to see this user
- if not request.access.has_team_access(
- Project.objects.select_related('team').get(pk=euser.project_id).team):
+ teams = Team.objects.filter(
+ organization=organization,
+ id__in=ProjectTeam.objects.filter(
+ project_id=euser.project_id,
+ ).values_list('team_id', flat=True)
+ )
+ has_team_access = any([request.access.has_team_access(t) for t in teams])
+
+ if not has_team_access:
return Response([])
other_eusers = euser.find_similar_users(request.user)
event_users = [euser] + list(other_eusers)
if event_users:
- tag_filters = [Q(value=eu.tag_value, project_id=eu.project_id)
- for eu in event_users]
- tags = GroupTagValue.objects.filter(
- reduce(or_, tag_filters),
- key='sentry:user',
- ).order_by('-last_seen')[:limit]
+ tags = tagstore.get_group_tag_values_for_users(event_users, limit=limit)
else:
- tags = GroupTagValue.objects.none()
+ tags = []
tags = {t.group_id: t for t in tags}
if tags:
@@ -55,6 +56,7 @@ def get(self, request, organization, user_id):
groups, request.user, TagBasedStreamGroupSerializer(
stats_period=None,
tags=tags,
+ environment_func=self._get_environment_func(request, organization.id)
)
)
diff --git a/src/sentry/api/endpoints/organization_user_issues_search.py b/src/sentry/api/endpoints/organization_user_issues_search.py
index ec143e0b3efc11..d403e01fc93e32 100644
--- a/src/sentry/api/endpoints/organization_user_issues_search.py
+++ b/src/sentry/api/endpoints/organization_user_issues_search.py
@@ -2,13 +2,15 @@
from rest_framework.response import Response
+from sentry import tagstore
+from sentry.api.base import EnvironmentMixin
from sentry.api.bases.organization import OrganizationEndpoint
from sentry.api.serializers import serialize
-from sentry.api.serializers.models.group import StreamGroupSerializer
-from sentry.models import (EventUser, Group, GroupTagValue, OrganizationMemberTeam, Project)
+from sentry.api.serializers.models.group import GroupSerializer
+from sentry.models import (EventUser, Group, OrganizationMemberTeam, Project)
-class OrganizationUserIssuesSearchEndpoint(OrganizationEndpoint):
+class OrganizationUserIssuesSearchEndpoint(OrganizationEndpoint, EnvironmentMixin):
def get(self, request, organization):
email = request.GET.get('email')
@@ -20,7 +22,7 @@ def get(self, request, organization):
# limit to only teams user has opted into
project_ids = list(
Project.objects.filter(
- team__in=OrganizationMemberTeam.objects.filter(
+ teams__in=OrganizationMemberTeam.objects.filter(
organizationmember__user=request.user,
organizationmember__organization=organization,
is_active=True,
@@ -35,18 +37,15 @@ def get(self, request, organization):
project_ids = list(set([e.project_id for e in event_users]))
- group_ids = list(
- GroupTagValue.objects.filter(
- key='sentry:user',
- value__in=[eu.tag_value for eu in event_users],
- project_id__in=project_ids,
- ).order_by('-last_seen').values_list('group_id', flat=True)[:limit]
- )
+ group_ids = tagstore.get_group_ids_for_users(project_ids, event_users, limit=limit)
groups = Group.objects.filter(
id__in=group_ids,
).order_by('-last_seen')[:limit]
- context = serialize(list(groups), request.user, StreamGroupSerializer(stats_period=None))
+ context = serialize(list(groups), request.user, GroupSerializer(
+ environment_func=self._get_environment_func(
+ request, organization.id)
+ ))
return Response(context)
diff --git a/src/sentry/api/endpoints/project_details.py b/src/sentry/api/endpoints/project_details.py
index cddb377cec1ad2..2837479b02feb3 100644
--- a/src/sentry/api/endpoints/project_details.py
+++ b/src/sentry/api/endpoints/project_details.py
@@ -9,6 +9,7 @@
from django.utils import timezone
from rest_framework import serializers, status
from rest_framework.response import Response
+
from sentry import features
from sentry.utils.data_filters import FilterTypes
from sentry.api.base import DocSection
@@ -16,9 +17,10 @@
from sentry.api.decorators import sudo_required
from sentry.api.serializers import serialize
from sentry.api.serializers.models.project import DetailedProjectSerializer
+from sentry.api.serializers.rest_framework import ListField, OriginField
from sentry.models import (
AuditLogEntryEvent, Group, GroupStatus, Project, ProjectBookmark, ProjectStatus,
- UserOption, Team,
+ ProjectTeam, UserOption, Team,
)
from sentry.tasks.deletion import delete_project
from sentry.utils.apidocs import scenario, attach_scenarios
@@ -71,12 +73,9 @@ def clean_newline_inputs(value, case_insensitive=True):
class ProjectMemberSerializer(serializers.Serializer):
isBookmarked = serializers.BooleanField()
isSubscribed = serializers.BooleanField()
- platform = serializers.CharField(required=False)
-class ProjectAdminSerializer(serializers.Serializer):
- isBookmarked = serializers.BooleanField()
- isSubscribed = serializers.BooleanField()
+class ProjectAdminSerializer(ProjectMemberSerializer):
name = serializers.CharField(max_length=200)
slug = serializers.RegexField(r'^[a-z0-9_\-]+$', max_length=50)
team = serializers.RegexField(r'^[a-z0-9_\-]+$', max_length=50)
@@ -84,6 +83,18 @@ class ProjectAdminSerializer(serializers.Serializer):
digestsMaxDelay = serializers.IntegerField(min_value=60, max_value=3600)
subjectPrefix = serializers.CharField(max_length=200)
subjectTemplate = serializers.CharField(max_length=200)
+ securityToken = serializers.RegexField(r'^[-a-zA-Z0-9+/=\s]+$', max_length=255)
+ securityTokenHeader = serializers.RegexField(r'^[a-zA-Z0-9_\-]+$', max_length=20)
+ verifySSL = serializers.BooleanField(required=False)
+ defaultEnvironment = serializers.CharField(required=False)
+ dataScrubber = serializers.BooleanField(required=False)
+ dataScrubberDefaults = serializers.BooleanField(required=False)
+ sensitiveFields = ListField(child=serializers.CharField(), required=False)
+ safeFields = ListField(child=serializers.CharField(), required=False)
+ scrubIPAddresses = serializers.BooleanField(required=False)
+ scrapeJavaScript = serializers.BooleanField(required=False)
+ allowedDomains = ListField(child=OriginField(), required=False)
+ resolveAge = serializers.IntegerField(required=False)
platform = serializers.CharField(required=False)
def validate_digestsMaxDelay(self, attrs, source):
@@ -93,6 +104,27 @@ def validate_digestsMaxDelay(self, attrs, source):
)
return attrs
+ def validate_allowedDomains(self, attrs, source):
+ attrs[source] = filter(bool, attrs[source])
+ if len(attrs[source]) == 0:
+ raise serializers.ValidationError(
+ 'Empty value will block all requests, use * to accept from all domains'
+ )
+ return attrs
+
+ def validate_slug(self, attrs, source):
+ slug = attrs[source]
+ project = self.context['project']
+ other = Project.objects.filter(
+ slug=slug,
+ organization=project.organization,
+ ).exclude(id=project.id).first()
+ if other is not None:
+ raise serializers.ValidationError(
+ 'Another project (%s) is already using that slug' % other.name
+ )
+ return attrs
+
class RelaxedProjectPermission(ProjectPermission):
scope_map = {
@@ -166,8 +198,6 @@ def put(self, request, project):
the bookmark flag.
:param int digestsMinDelay:
:param int digestsMaxDelay:
- :param object options: optional options to override in the
- project settings.
:auth: required
"""
has_project_write = (
@@ -180,7 +210,14 @@ def put(self, request, project):
else:
serializer_cls = ProjectMemberSerializer
- serializer = serializer_cls(data=request.DATA, partial=True)
+ serializer = serializer_cls(
+ data=request.DATA,
+ partial=True,
+ context={
+ 'project': project,
+ 'request': request,
+ },
+ )
if not serializer.is_valid():
return Response(serializer.errors, status=400)
@@ -205,6 +242,7 @@ def put(self, request, project):
project.name = result['name']
changed = True
+ old_team_id = None
if result.get('team'):
team_list = [
t for t in Team.objects.get_for_user(
@@ -220,6 +258,7 @@ def put(self, request, project):
'detail': ['The new team is not found.']
}, status=400
)
+ old_team_id = project.team_id
project.team = team_list[0]
changed = True
@@ -229,6 +268,11 @@ def put(self, request, project):
if changed:
project.save()
+ if old_team_id is not None:
+ ProjectTeam.objects.filter(
+ project=project,
+ team_id=old_team_id,
+ ).update(team=project.team)
if result.get('isBookmarked'):
try:
@@ -251,12 +295,40 @@ def put(self, request, project):
if result.get('digestsMaxDelay'):
project.update_option(
'digests:mail:maximum_delay', result['digestsMaxDelay'])
- if result.get('subjectPrefix'):
+ if result.get('subjectPrefix') is not None:
project.update_option('mail:subject_prefix',
result['subjectPrefix'])
if result.get('subjectTemplate'):
project.update_option('mail:subject_template',
result['subjectTemplate'])
+ if result.get('defaultEnvironment') is not None:
+ project.update_option('sentry:default_environment', result['defaultEnvironment'])
+ if result.get('scrubIPAddresses') is not None:
+ project.update_option('sentry:scrub_ip_address', result['scrubIPAddresses'])
+ if result.get('securityToken') is not None:
+ project.update_option('sentry:token', result['securityToken'])
+ if result.get('securityTokenHeader') is not None:
+ project.update_option('sentry:token_header', result['securityTokenHeader'])
+ if result.get('verifySSL') is not None:
+ project.update_option('sentry:verify_ssl', result['verifySSL'])
+ if result.get('dataScrubber') is not None:
+ project.update_option('sentry:scrub_data', result['dataScrubber'])
+ if result.get('dataScrubberDefaults') is not None:
+ project.update_option('sentry:scrub_defaults', result['dataScrubberDefaults'])
+ if result.get('sensitiveFields') is not None:
+ project.update_option('sentry:sensitive_fields', result['sensitiveFields'])
+ if result.get('safeFields') is not None:
+ project.update_option('sentry:safe_fields', result['safeFields'])
+ # resolveAge can be None
+ if 'resolveAge' in result:
+ project.update_option(
+ 'sentry:resolve_age',
+ 0 if result.get('resolveAge') is None else int(
+ result['resolveAge']))
+ if result.get('scrapeJavaScript') is not None:
+ project.update_option('sentry:scrape_javascript', result['scrapeJavaScript'])
+ if result.get('allowedDomains'):
+ project.update_option('sentry:origins', result['allowedDomains'])
if result.get('isSubscribed'):
UserOption.objects.set_value(
@@ -267,6 +339,7 @@ def put(self, request, project):
user=request.user, key='mail:alert', value=0, project=project
)
+ # TODO(dcramer): rewrite options to use standard API config
if has_project_write:
options = request.DATA.get('options', {})
if 'sentry:origins' in options:
@@ -296,6 +369,21 @@ def put(self, request, project):
[s.strip().lower()
for s in options['sentry:sensitive_fields']]
)
+ if 'sentry:scrub_ip_address' in options:
+ project.update_option(
+ 'sentry:scrub_ip_address',
+ bool(options['sentry:scrub_ip_address']),
+ )
+ if 'mail:subject_prefix' in options:
+ project.update_option(
+ 'mail:subject_prefix',
+ options['mail:subject_prefix'],
+ )
+ if 'sentry:default_environment' in options:
+ project.update_option(
+ 'sentry:default_environment',
+ options['sentry:default_environment'],
+ )
if 'sentry:csp_ignored_sources_defaults' in options:
project.update_option(
'sentry:csp_ignored_sources_defaults',
@@ -306,6 +394,11 @@ def put(self, request, project):
'sentry:csp_ignored_sources',
clean_newline_inputs(options['sentry:csp_ignored_sources'])
)
+ if 'sentry:blacklisted_ips' in options:
+ project.update_option(
+ 'sentry:blacklisted_ips',
+ clean_newline_inputs(options['sentry:blacklisted_ips']),
+ )
if 'feedback:branding' in options:
project.update_option(
'feedback:branding', '1' if options['feedback:branding'] else '0'
diff --git a/src/sentry/api/endpoints/project_group_index.py b/src/sentry/api/endpoints/project_group_index.py
index 930fb73407ce06..c81826707855e3 100644
--- a/src/sentry/api/endpoints/project_group_index.py
+++ b/src/sentry/api/endpoints/project_group_index.py
@@ -1,6 +1,7 @@
from __future__ import absolute_import, division, print_function
from datetime import timedelta
+import functools
import logging
from uuid import uuid4
@@ -10,8 +11,8 @@
from rest_framework import serializers
from rest_framework.response import Response
-from sentry import features, search, tagstore
-from sentry.api.base import DocSection
+from sentry import features, search
+from sentry.api.base import DocSection, EnvironmentMixin
from sentry.api.bases.project import ProjectEndpoint, ProjectEventPermission
from sentry.api.fields import UserField
from sentry.api.serializers import serialize
@@ -20,9 +21,9 @@
from sentry.constants import DEFAULT_SORT_OPTION
from sentry.db.models.query import create_or_update
from sentry.models import (
- Activity, EventMapping, Group, GroupAssignee, GroupBookmark, GroupHash, GroupResolution,
- GroupSeen, GroupSnooze, GroupStatus, GroupSubscription, GroupSubscriptionReason, GroupTombstone,
- Release, TOMBSTONE_FIELDS_FROM_GROUP, UserOption
+ Activity, Environment, Group, GroupAssignee, GroupBookmark, GroupHash, GroupResolution,
+ GroupSeen, GroupShare, GroupSnooze, GroupStatus, GroupSubscription, GroupSubscriptionReason,
+ GroupTombstone, Release, TOMBSTONE_FIELDS_FROM_GROUP, UserOption
)
from sentry.models.event import Event
from sentry.models.group import looks_like_short_id
@@ -32,7 +33,7 @@
from sentry.tasks.deletion import delete_group
from sentry.tasks.merge import merge_group
from sentry.utils.apidocs import attach_scenarios, scenario
-from sentry.utils.cursors import Cursor
+from sentry.utils.cursors import Cursor, CursorResult
from sentry.utils.functional import extract_lazy_object
delete_logger = logging.getLogger('sentry.deletions.api')
@@ -95,11 +96,11 @@ class StatusDetailsValidator(serializers.Serializer):
inRelease = serializers.CharField()
ignoreDuration = serializers.IntegerField()
ignoreCount = serializers.IntegerField()
- # in hours, max of one week
- ignoreWindow = serializers.IntegerField(max_value=7 * 24)
+ # in minutes, max of one week
+ ignoreWindow = serializers.IntegerField(max_value=7 * 24 * 60)
ignoreUserCount = serializers.IntegerField()
- # in hours, max of one week
- ignoreUserWindow = serializers.IntegerField(max_value=7 * 24)
+ # in minutes, max of one week
+ ignoreUserWindow = serializers.IntegerField(max_value=7 * 24 * 60)
def validate_inRelease(self, attrs, source):
value = attrs[source]
@@ -109,7 +110,9 @@ def validate_inRelease(self, attrs, source):
attrs[source] = Release.objects.filter(
projects=project,
organization_id=project.organization_id,
- ).order_by('-date_added')[0]
+ ).extra(select={
+ 'sort': 'COALESCE(date_released, date_added)',
+ }).order_by('-sort')[0]
except IndexError:
raise serializers.ValidationError(
'No release data present in the system to form a basis for \'Next Release\''
@@ -151,11 +154,11 @@ class GroupValidator(serializers.Serializer):
discard = serializers.BooleanField()
ignoreDuration = serializers.IntegerField()
ignoreCount = serializers.IntegerField()
- # in hours, max of one week
- ignoreWindow = serializers.IntegerField(max_value=7 * 24)
+ # in minutes, max of one week
+ ignoreWindow = serializers.IntegerField(max_value=7 * 24 * 60)
ignoreUserCount = serializers.IntegerField()
- # in hours, max of one week
- ignoreUserWindow = serializers.IntegerField(max_value=7 * 24)
+ # in minutes, max of one week
+ ignoreUserWindow = serializers.IntegerField(max_value=7 * 24 * 60)
assignedTo = UserField()
# TODO(dcramer): remove in 9.0
@@ -176,7 +179,7 @@ def validate(self, attrs):
return attrs
-class ProjectGroupIndexEndpoint(ProjectEndpoint):
+class ProjectGroupIndexEndpoint(ProjectEndpoint, EnvironmentMixin):
doc_section = DocSection.EVENTS
permission_classes = (ProjectEventPermission, )
@@ -184,33 +187,9 @@ class ProjectGroupIndexEndpoint(ProjectEndpoint):
def _build_query_params_from_request(self, request, project):
query_kwargs = {
'project': project,
+ 'sort_by': request.GET.get('sort', DEFAULT_SORT_OPTION),
}
- if request.GET.get('status'):
- try:
- query_kwargs['status'] = STATUS_CHOICES[request.GET['status']]
- except KeyError:
- raise ValidationError('invalid status')
-
- if request.user.is_authenticated() and request.GET.get('bookmarks'):
- query_kwargs['bookmarked_by'] = request.user
-
- if request.user.is_authenticated() and request.GET.get('assigned'):
- query_kwargs['assigned_to'] = request.user
-
- sort_by = request.GET.get('sort')
- if sort_by is None:
- sort_by = DEFAULT_SORT_OPTION
-
- query_kwargs['sort_by'] = sort_by
-
- tags = {}
- for tag_key in (tk.key for tk in tagstore.get_tag_keys(project.id)):
- if request.GET.get(tag_key):
- tags[tag_key] = request.GET[tag_key]
- if tags:
- query_kwargs['tags'] = tags
-
limit = request.GET.get('limit')
if limit:
try:
@@ -248,9 +227,6 @@ def _subscribe_and_assign_issue(self, acting_user, group, result):
if self_assign_issue == '1' and not group.assignee_set.exists():
result['assignedTo'] = extract_lazy_object(acting_user)
- # bookmarks=0/1
- # status=
- # =
# statsPeriod=24h
@attach_scenarios([list_project_issues_scenario])
def get(self, request, project):
@@ -296,22 +272,23 @@ def get(self, request, project):
# disable stats
stats_period = None
- query = request.GET.get('query', '').strip()
+ serializer = functools.partial(
+ StreamGroupSerializer,
+ environment_func=self._get_environment_func(request, project.organization_id),
+ stats_period=stats_period,
+ )
+ query = request.GET.get('query', '').strip()
if query:
matching_group = None
matching_event = None
if len(query) == 32:
# check to see if we've got an event ID
try:
- mapping = EventMapping.objects.get(
- project_id=project.id,
- event_id=query,
- )
- except EventMapping.DoesNotExist:
+ matching_group = Group.objects.from_event_id(project, query)
+ except Group.DoesNotExist:
pass
else:
- matching_group = Group.objects.get(id=mapping.group_id)
try:
matching_event = Event.objects.get(
event_id=query, project_id=project.id)
@@ -333,11 +310,8 @@ def get(self, request, project):
if matching_group is not None:
response = Response(
serialize(
- [matching_group], request.user,
- StreamGroupSerializer(
- stats_period=stats_period,
- matching_event_id=getattr(
- matching_event, 'id', None)
+ [matching_group], request.user, serializer(
+ matching_event_id=getattr(matching_event, 'id', None),
)
)
)
@@ -350,13 +324,22 @@ def get(self, request, project):
except ValidationError as exc:
return Response({'detail': six.text_type(exc)}, status=400)
- cursor_result = search.query(count_hits=True, **query_kwargs)
+ try:
+ environment_id = self._get_environment_id_from_request(
+ request, project.organization_id)
+ except Environment.DoesNotExist:
+ # XXX: The 1000 magic number for `max_hits` is an abstraction leak
+ # from `sentry.api.paginator.BasePaginator.get_result`.
+ cursor_result = CursorResult([], None, None, hits=0, max_hits=1000)
+ else:
+ cursor_result = search.query(
+ count_hits=True,
+ environment_id=environment_id,
+ **query_kwargs)
results = list(cursor_result)
- context = serialize(
- results, request.user, StreamGroupSerializer(
- stats_period=stats_period))
+ context = serialize(results, request.user, serializer())
# HACK: remove auto resolved entries
if query_kwargs.get('status') == GroupStatus.UNRESOLVED:
@@ -406,7 +389,7 @@ def put(self, request, project):
:pparam string project_slug: the slug of the project the issues
belong to.
:param string status: the new status for the issues. Valid values
- are ``"resolved"``, ``resolvedInNextRelease``,
+ are ``"resolved"``, ``"resolvedInNextRelease"``,
``"unresolved"``, and ``"ignored"``.
:param int ignoreDuration: the number of minutes to ignore this issue.
:param boolean isPublic: sets the issue to public or private.
@@ -473,7 +456,7 @@ def put(self, request, project):
discard = result.get('discard')
if discard:
- if not features.has('projects:custom-filters', project, actor=request.user):
+ if not features.has('projects:discard-groups', project, actor=request.user):
return Response({'detail': ['You do not have that feature enabled']}, status=400)
group_list = list(queryset)
@@ -512,7 +495,9 @@ def put(self, request, project):
release = Release.objects.filter(
projects=project,
organization_id=project.organization_id,
- ).order_by('-date_added')[0]
+ ).extra(select={
+ 'sort': 'COALESCE(date_released, date_added)',
+ }).order_by('-sort')[0]
activity_type = Activity.SET_RESOLVED_IN_RELEASE
activity_data = {
# no version yet
@@ -799,30 +784,35 @@ def put(self, request, project):
),
}
- if result.get('isPublic'):
- queryset.update(is_public=True)
+ if 'isPublic' in result:
+ # We always want to delete an existing share, because triggering
+ # an isPublic=True even when it's already public, should trigger
+ # regenerating.
for group in group_list:
- if group.is_public:
- continue
- group.is_public = True
- Activity.objects.create(
- project=group.project,
- group=group,
- type=Activity.SET_PUBLIC,
- user=acting_user,
- )
- elif result.get('isPublic') is False:
- queryset.update(is_public=False)
+ if GroupShare.objects.filter(group=group).delete():
+ result['shareId'] = None
+ Activity.objects.create(
+ project=group.project,
+ group=group,
+ type=Activity.SET_PRIVATE,
+ user=acting_user,
+ )
+
+ if result.get('isPublic'):
for group in group_list:
- if not group.is_public:
- continue
- group.is_public = False
- Activity.objects.create(
+ share, created = GroupShare.objects.get_or_create(
project=group.project,
group=group,
- type=Activity.SET_PRIVATE,
user=acting_user,
)
+ if created:
+ result['shareId'] = share.uuid
+ Activity.objects.create(
+ project=group.project,
+ group=group,
+ type=Activity.SET_PUBLIC,
+ user=acting_user,
+ )
# XXX(dcramer): this feels a bit shady like it should be its own
# endpoint
diff --git a/src/sentry/api/endpoints/project_group_stats.py b/src/sentry/api/endpoints/project_group_stats.py
index a0e3a8a429d174..f5157d974aa8ae 100644
--- a/src/sentry/api/endpoints/project_group_stats.py
+++ b/src/sentry/api/endpoints/project_group_stats.py
@@ -5,13 +5,22 @@
from rest_framework.response import Response
from sentry.app import tsdb
-from sentry.api.base import StatsMixin
+from sentry.api.base import EnvironmentMixin, StatsMixin
from sentry.api.bases.project import ProjectEndpoint
-from sentry.models import Group
+from sentry.api.exceptions import ResourceDoesNotExist
+from sentry.models import Environment, Group
-class ProjectGroupStatsEndpoint(ProjectEndpoint, StatsMixin):
+class ProjectGroupStatsEndpoint(ProjectEndpoint, EnvironmentMixin, StatsMixin):
def get(self, request, project):
+ try:
+ environment_id = self._get_environment_id_from_request(
+ request,
+ project.organization_id,
+ )
+ except Environment.DoesNotExist:
+ raise ResourceDoesNotExist
+
group_ids = request.GET.getlist('id')
if not group_ids:
return Response(status=204)
@@ -22,6 +31,11 @@ def get(self, request, project):
if not group_ids:
return Response(status=204)
- data = tsdb.get_range(model=tsdb.models.group, keys=group_ids, **self._parse_args(request))
+ data = tsdb.get_range(
+ model=tsdb.models.group, keys=group_ids, **self._parse_args(
+ request,
+ environment_id,
+ )
+ )
return Response({six.text_type(k): v for k, v in data.items()})
diff --git a/src/sentry/api/endpoints/project_index.py b/src/sentry/api/endpoints/project_index.py
index 111dedb103b45f..94bdf39a80d410 100644
--- a/src/sentry/api/endpoints/project_index.py
+++ b/src/sentry/api/endpoints/project_index.py
@@ -8,6 +8,7 @@
from sentry.api.bases.project import ProjectPermission
from sentry.api.paginator import DateTimePaginator
from sentry.api.serializers import serialize, ProjectWithOrganizationSerializer
+from sentry.auth.superuser import is_active_superuser
from sentry.db.models.query import in_iexact
from sentry.models import (Project, ProjectPlatform, ProjectStatus)
from sentry.search.utils import tokenize_query
@@ -59,9 +60,9 @@ def get(self, request):
)
else:
queryset = queryset.none()
- elif not request.is_superuser():
+ elif not is_active_superuser(request):
queryset = queryset.filter(
- team__organizationmember__user=request.user,
+ teams__organizationmember__user=request.user,
)
query = request.GET.get('query')
diff --git a/src/sentry/api/endpoints/project_key_details.py b/src/sentry/api/endpoints/project_key_details.py
index 20da7613ebb10c..508f09162469c5 100644
--- a/src/sentry/api/endpoints/project_key_details.py
+++ b/src/sentry/api/endpoints/project_key_details.py
@@ -35,7 +35,7 @@ def update_key_scenario(runner):
class RateLimitSerializer(serializers.Serializer):
count = serializers.IntegerField(min_value=0, required=False)
- window = serializers.IntegerField(min_value=0, max_value=60 * 24, required=False)
+ window = serializers.IntegerField(min_value=0, max_value=60 * 60 * 24, required=False)
class KeySerializer(serializers.Serializer):
diff --git a/src/sentry/api/endpoints/project_member_index.py b/src/sentry/api/endpoints/project_member_index.py
index e0d9fd3c20bc57..090052085bcd89 100644
--- a/src/sentry/api/endpoints/project_member_index.py
+++ b/src/sentry/api/endpoints/project_member_index.py
@@ -13,7 +13,7 @@ def get(self, request, project):
queryset = OrganizationMember.objects.filter(
Q(user__is_active=True) | Q(user__isnull=True),
organization=project.organization,
- teams=project.team,
+ teams=project.teams.all(),
).select_related('user')
member_list = sorted(
diff --git a/src/sentry/api/endpoints/project_platforms.py b/src/sentry/api/endpoints/project_platforms.py
index 769110dbdf7219..87ed7c963ed4d5 100644
--- a/src/sentry/api/endpoints/project_platforms.py
+++ b/src/sentry/api/endpoints/project_platforms.py
@@ -2,14 +2,8 @@
from rest_framework.response import Response
from sentry.api.bases.project import ProjectEndpoint
-from sentry.api.serializers import serialize, register, Serializer
from sentry.models import ProjectPlatform
-
-
-@register(ProjectPlatform)
-class ProjectPlatformSerializer(Serializer):
- def serialize(self, obj, attrs, user):
- return {'platform': obj.platform, 'dateCreated': obj.date_added}
+from sentry.api.serializers import serialize
class ProjectPlatformsEndpoint(ProjectEndpoint):
diff --git a/src/sentry/api/endpoints/project_plugin_details.py b/src/sentry/api/endpoints/project_plugin_details.py
index fb1ae3acc24c50..30deefb8f8caa7 100644
--- a/src/sentry/api/endpoints/project_plugin_details.py
+++ b/src/sentry/api/endpoints/project_plugin_details.py
@@ -46,7 +46,7 @@ def get(self, request, project, plugin_id):
def post(self, request, project, plugin_id):
"""
- Enable plugin or Test plugin
+ Enable plugin, Test plugin or Reset plugin values
"""
plugin = self._get_plugin(plugin_id)
@@ -66,6 +66,12 @@ def post(self, request, project, plugin_id):
test_results = 'No errors returned'
return Response({'detail': test_results}, status=200)
+ if request.DATA.get('reset'):
+ plugin = self._get_plugin(plugin_id)
+ plugin.reset_options(project=project)
+ context = serialize(plugin, request.user, PluginWithConfigSerializer(project))
+ return Response(context, status=200)
+
if not plugin.can_disable:
return Response({'detail': ERR_ALWAYS_ENABLED}, status=400)
@@ -94,6 +100,7 @@ def put(self, request, project, plugin_id):
for c in plugin.get_config(
project=project,
user=request.user,
+ initial=request.DATA,
)
]
diff --git a/src/sentry/api/endpoints/project_plugins.py b/src/sentry/api/endpoints/project_plugins.py
index d1052ebc8d2b0c..6350a964031731 100644
--- a/src/sentry/api/endpoints/project_plugins.py
+++ b/src/sentry/api/endpoints/project_plugins.py
@@ -13,7 +13,6 @@ def get(self, request, project):
context = serialize(
[
plugin for plugin in plugins.configurable_for_project(project, version=None)
- if plugin.has_plugin_conf()
], request.user, PluginSerializer(project)
)
return Response(context)
diff --git a/src/sentry/api/endpoints/project_processingissues.py b/src/sentry/api/endpoints/project_processingissues.py
index 97286289db68a3..e7de0818d9b05d 100644
--- a/src/sentry/api/endpoints/project_processingissues.py
+++ b/src/sentry/api/endpoints/project_processingissues.py
@@ -51,6 +51,7 @@ def get(self, request, project):
],
'project':
project,
+ 'team': project.teams.first(),
'token':
token,
'server_url':
diff --git a/src/sentry/api/endpoints/project_release_details.py b/src/sentry/api/endpoints/project_release_details.py
index b437b08c733474..721bce10a8ebfd 100644
--- a/src/sentry/api/endpoints/project_release_details.py
+++ b/src/sentry/api/endpoints/project_release_details.py
@@ -9,12 +9,13 @@
from sentry.api.serializers.rest_framework import CommitSerializer, ListField
from sentry.models import Activity, Group, Release, ReleaseFile
from sentry.plugins.interfaces.releasehook import ReleaseHook
+from sentry.constants import VERSION_LENGTH
ERR_RELEASE_REFERENCED = "This release is referenced by active issues and cannot be removed."
class ReleaseSerializer(serializers.Serializer):
- ref = serializers.CharField(max_length=64, required=False)
+ ref = serializers.CharField(max_length=VERSION_LENGTH, required=False)
url = serializers.URLField(required=False)
dateReleased = serializers.DateTimeField(required=False)
commits = ListField(child=CommitSerializer(), required=False, allow_null=False)
@@ -110,7 +111,7 @@ def put(self, request, project, version):
Activity.objects.create(
type=Activity.RELEASE,
project=project,
- ident=release.version,
+ ident=Activity.get_version_ident(release.version),
data={'version': release.version},
datetime=release.date_released,
)
diff --git a/src/sentry/api/endpoints/project_releases.py b/src/sentry/api/endpoints/project_releases.py
index 8961fa3e71efc1..fa23a58e0c740c 100644
--- a/src/sentry/api/endpoints/project_releases.py
+++ b/src/sentry/api/endpoints/project_releases.py
@@ -12,13 +12,14 @@
from sentry.api.serializers.rest_framework import CommitSerializer, ListField
from sentry.models import Activity, Release
from sentry.plugins.interfaces.releasehook import ReleaseHook
+from sentry.constants import VERSION_LENGTH
BAD_RELEASE_CHARS = '\n\f\t/'
class ReleaseSerializer(serializers.Serializer):
- version = serializers.CharField(max_length=64, required=True)
- ref = serializers.CharField(max_length=64, required=False)
+ version = serializers.CharField(max_length=VERSION_LENGTH, required=True)
+ ref = serializers.CharField(max_length=VERSION_LENGTH, required=False)
url = serializers.URLField(required=False)
owner = UserField(required=False)
dateReleased = serializers.DateTimeField(required=False)
@@ -140,7 +141,7 @@ def post(self, request, project):
Activity.objects.create(
type=Activity.RELEASE,
project=project,
- ident=result['version'],
+ ident=Activity.get_version_ident(result['version']),
data={'version': result['version']},
datetime=release.date_released,
)
diff --git a/src/sentry/api/endpoints/project_rules_configuration.py b/src/sentry/api/endpoints/project_rules_configuration.py
new file mode 100644
index 00000000000000..49fc8325fdc828
--- /dev/null
+++ b/src/sentry/api/endpoints/project_rules_configuration.py
@@ -0,0 +1,42 @@
+from __future__ import absolute_import
+
+
+from sentry.api.bases.project import ProjectEndpoint, StrictProjectPermission
+from sentry.rules import rules
+from rest_framework.response import Response
+
+
+class ProjectRulesConfigurationEndpoint(ProjectEndpoint):
+ permission_classes = (StrictProjectPermission, )
+
+ def get(self, request, project):
+ """
+ Retrieve the list of configuration options for a given project.
+ """
+
+ action_list = []
+ condition_list = []
+
+ # TODO: conditions need to be based on actions
+ for rule_type, rule_cls in rules:
+ node = rule_cls(project)
+ context = {
+ 'id': node.id,
+ 'label': node.label,
+ 'html': node.render_form(),
+ }
+
+ if not node.is_enabled():
+ continue
+
+ if rule_type.startswith('condition/'):
+ condition_list.append(context)
+ elif rule_type.startswith('action/'):
+ action_list.append(context)
+
+ context = {
+ 'actions': action_list,
+ 'conditions': condition_list
+ }
+
+ return Response(context)
diff --git a/src/sentry/api/endpoints/project_search_details.py b/src/sentry/api/endpoints/project_search_details.py
index d44a0da7006b18..93a3335bc397b6 100644
--- a/src/sentry/api/endpoints/project_search_details.py
+++ b/src/sentry/api/endpoints/project_search_details.py
@@ -64,7 +64,10 @@ def put(self, request, project, search_id):
except SavedSearch.DoesNotExist:
raise ResourceDoesNotExist
- if request.access.has_team_scope(project.team, 'project:write'):
+ has_team_scope = any(
+ request.access.has_team_scope(team, 'project:write') for team in project.teams.all()
+ )
+ if has_team_scope:
serializer = SavedSearchSerializer(data=request.DATA, partial=True)
else:
serializer = LimitedSavedSearchSerializer(data=request.DATA, partial=True)
diff --git a/src/sentry/api/endpoints/project_servicehook_details.py b/src/sentry/api/endpoints/project_servicehook_details.py
new file mode 100644
index 00000000000000..1af98dd90c2c36
--- /dev/null
+++ b/src/sentry/api/endpoints/project_servicehook_details.py
@@ -0,0 +1,123 @@
+from __future__ import absolute_import
+
+from django.db import transaction
+from rest_framework import status
+
+from sentry.api.base import DocSection
+from sentry.api.bases.project import ProjectEndpoint
+from sentry.api.exceptions import ResourceDoesNotExist
+from sentry.api.serializers import serialize
+from sentry.api.validators import ServiceHookValidator
+from sentry.models import AuditLogEntryEvent, ServiceHook
+
+
+class ProjectServiceHookDetailsEndpoint(ProjectEndpoint):
+ doc_section = DocSection.PROJECTS
+
+ def get(self, request, project, hook_id):
+ """
+ Retrieve a Service Hooks
+ ````````````````````````
+
+ Return a service hook bound to a project.
+
+ :pparam string organization_slug: the slug of the organization the
+ client keys belong to.
+ :pparam string project_slug: the slug of the project the client keys
+ belong to.
+ :pparam string hook_id: the guid of the service hook.
+ """
+ try:
+ hook = ServiceHook.objects.get(
+ project_id=project.id,
+ guid=hook_id,
+ )
+ except ServiceHook.DoesNotExist:
+ raise ResourceDoesNotExist
+ return self.respond(serialize(hook, request.user))
+
+ def put(self, request, project, hook_id):
+ """
+ Update a Service Hook
+ `````````````````````
+
+ :pparam string organization_slug: the slug of the organization the
+ client keys belong to.
+ :pparam string project_slug: the slug of the project the client keys
+ belong to.
+ :pparam string hook_id: the guid of the service hook.
+ :param string url: the url for the webhook
+ :param array[string] events: the events to subscribe to
+ """
+ if not request.user.is_authenticated():
+ return self.respond(status=401)
+
+ try:
+ hook = ServiceHook.objects.get(
+ project_id=project.id,
+ guid=hook_id,
+ )
+ except ServiceHook.DoesNotExist:
+ raise ResourceDoesNotExist
+
+ validator = ServiceHookValidator(data=request.DATA, partial=True)
+ if not validator.is_valid():
+ return self.respond(validator.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ result = validator.object
+
+ updates = {}
+ if result.get('events'):
+ updates['events'] = result['events']
+ if result.get('url'):
+ updates['url'] = result['url']
+ if result.get('version') is not None:
+ updates['version'] = result['version']
+
+ with transaction.atomic():
+ hook.update(**updates)
+
+ self.create_audit_entry(
+ request=request,
+ organization=project.organization,
+ target_object=hook.id,
+ event=AuditLogEntryEvent.SERVICEHOOK_EDIT,
+ data=hook.get_audit_log_data(),
+ )
+
+ return self.respond(serialize(hook, request.user))
+
+ def delete(self, request, project, hook_id):
+ """
+ Remove a Service Hook
+ `````````````````````
+
+ :pparam string organization_slug: the slug of the organization the
+ client keys belong to.
+ :pparam string project_slug: the slug of the project the client keys
+ belong to.
+ :pparam string hook_id: the guid of the service hook.
+ """
+ if not request.user.is_authenticated():
+ return self.respond(status=401)
+
+ try:
+ hook = ServiceHook.objects.get(
+ project_id=project.id,
+ guid=hook_id,
+ )
+ except ServiceHook.DoesNotExist:
+ raise ResourceDoesNotExist
+
+ with transaction.atomic():
+ hook.delete()
+
+ self.create_audit_entry(
+ request=request,
+ organization=project.organization,
+ target_object=hook.id,
+ event=AuditLogEntryEvent.SERVICEHOOK_REMOVE,
+ data=hook.get_audit_log_data(),
+ )
+
+ return self.respond(serialize(hook, request.user), status=204)
diff --git a/src/sentry/api/endpoints/project_servicehooks.py b/src/sentry/api/endpoints/project_servicehooks.py
new file mode 100644
index 00000000000000..e2d3808a531e99
--- /dev/null
+++ b/src/sentry/api/endpoints/project_servicehooks.py
@@ -0,0 +1,123 @@
+from __future__ import absolute_import
+
+from django.db import transaction
+from rest_framework import status
+
+from sentry import features
+from sentry.api.base import DocSection
+from sentry.api.bases.project import ProjectEndpoint
+from sentry.api.serializers import serialize
+from sentry.api.validators import ServiceHookValidator
+from sentry.models import AuditLogEntryEvent, ObjectStatus, ServiceHook
+from sentry.utils.apidocs import scenario, attach_scenarios
+
+
+@scenario('ListServiceHooks')
+def list_hooks_scenario(runner):
+ runner.request(
+ method='GET', path='/projects/%s/%s/hooks/' % (runner.org.slug, runner.default_project.slug)
+ )
+
+
+@scenario('CreateServiceHook')
+def create_hook_scenario(runner):
+ runner.request(
+ method='POST',
+ path='/projects/%s/%s/hooks/' % (runner.org.slug, runner.default_project.slug),
+ data={'url': 'https://example.com/sentry-hook', 'events': ['event.alert', 'event.created']}
+ )
+
+
+class ProjectServiceHooksEndpoint(ProjectEndpoint):
+ doc_section = DocSection.PROJECTS
+
+ def has_feature(self, request, project):
+ return features.has(
+ 'projects:servicehooks',
+ project=project,
+ actor=request.user,
+ )
+
+ @attach_scenarios([list_hooks_scenario])
+ def get(self, request, project):
+ """
+ List a Project's Service Hooks
+ ``````````````````````````````
+
+ Return a list of service hooks bound to a project.
+
+ :pparam string organization_slug: the slug of the organization the
+ client keys belong to.
+ :pparam string project_slug: the slug of the project the client keys
+ belong to.
+ """
+ queryset = ServiceHook.objects.filter(
+ project_id=project.id,
+ )
+ status = request.GET.get('status')
+ if status == 'active':
+ queryset = queryset.filter(
+ status=ObjectStatus.ACTIVE,
+ )
+ elif status == 'disabled':
+ queryset = queryset.filter(
+ status=ObjectStatus.DISABLED,
+ )
+ elif status:
+ queryset = queryset.none()
+
+ return self.paginate(
+ request=request,
+ queryset=queryset,
+ order_by='-id',
+ on_results=lambda x: serialize(x, request.user),
+ )
+
+ @attach_scenarios([create_hook_scenario])
+ def post(self, request, project):
+ """
+ Register a new Service Hook
+ ```````````````````````````
+
+ Create a new client key bound to a project. The key's secret and
+ public key are generated by the server.
+
+ Events include:
+
+ - event.alert: An alert is generated for an event (via rules).
+ - event.created: A new event has been processed.
+
+ :pparam string organization_slug: the slug of the organization the
+ client keys belong to.
+ :pparam string project_slug: the slug of the project the client keys
+ belong to.
+ :param string url: the url for the webhook
+ :param array[string] events: the events to subscribe to
+ """
+ if not request.user.is_authenticated():
+ return self.respond(status=401)
+
+ validator = ServiceHookValidator(data=request.DATA)
+ if not validator.is_valid():
+ return self.respond(validator.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ result = validator.object
+
+ with transaction.atomic():
+ hook = ServiceHook.objects.create(
+ project_id=project.id,
+ url=result['url'],
+ actor_id=request.user.id,
+ events=result.get('events'),
+ application=getattr(request.auth, 'application', None) if request.auth else None,
+ )
+
+ self.create_audit_entry(
+ request=request,
+ organization=project.organization,
+ target_object=hook.id,
+ event=AuditLogEntryEvent.SERVICEHOOK_ADD,
+ data=hook.get_audit_log_data(),
+ )
+
+ return self.respond(serialize(hook, request.user), status=201)
diff --git a/src/sentry/api/endpoints/project_stats.py b/src/sentry/api/endpoints/project_stats.py
index ab99f1028a1e69..54ec040eb8293d 100644
--- a/src/sentry/api/endpoints/project_stats.py
+++ b/src/sentry/api/endpoints/project_stats.py
@@ -3,8 +3,10 @@
from rest_framework.response import Response
from sentry import tsdb
-from sentry.api.base import DocSection, StatsMixin
+from sentry.api.base import DocSection, EnvironmentMixin, StatsMixin
from sentry.api.bases.project import ProjectEndpoint
+from sentry.api.exceptions import ResourceDoesNotExist
+from sentry.models import Environment
from sentry.utils.data_filters import FILTER_STAT_KEYS_TO_VALUES
from sentry.utils.apidocs import scenario, attach_scenarios
@@ -18,7 +20,7 @@ def retrieve_event_counts_project(runner):
)
-class ProjectStatsEndpoint(ProjectEndpoint, StatsMixin):
+class ProjectStatsEndpoint(ProjectEndpoint, EnvironmentMixin, StatsMixin):
doc_section = DocSection.PROJECTS
@attach_scenarios([retrieve_event_counts_project])
@@ -52,6 +54,7 @@ def get(self, request, project):
:auth: required
"""
stat = request.GET.get('stat', 'received')
+ query_kwargs = {}
if stat == 'received':
stat_model = tsdb.models.project_total_received
elif stat == 'rejected':
@@ -60,6 +63,13 @@ def get(self, request, project):
stat_model = tsdb.models.project_total_blacklisted
elif stat == 'generated':
stat_model = tsdb.models.project
+ try:
+ query_kwargs['environment_id'] = self._get_environment_id_from_request(
+ request,
+ project.organization_id,
+ )
+ except Environment.DoesNotExist:
+ raise ResourceDoesNotExist
elif stat == 'forwarded':
stat_model = tsdb.models.project_total_forwarded
else:
@@ -69,7 +79,7 @@ def get(self, request, project):
raise ValueError('Invalid stat: %s' % stat)
data = tsdb.get_range(
- model=stat_model, keys=[project.id], **self._parse_args(request)
+ model=stat_model, keys=[project.id], **self._parse_args(request, **query_kwargs)
)[project.id]
return Response(data)
diff --git a/src/sentry/api/endpoints/project_tagkey_details.py b/src/sentry/api/endpoints/project_tagkey_details.py
index e40ea07f38b6bd..44a4e412f8eb2d 100644
--- a/src/sentry/api/endpoints/project_tagkey_details.py
+++ b/src/sentry/api/endpoints/project_tagkey_details.py
@@ -3,18 +3,26 @@
from rest_framework.response import Response
from sentry import tagstore
+from sentry.api.base import EnvironmentMixin
from sentry.api.bases.project import ProjectEndpoint
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.api.serializers import serialize
-from sentry.models import AuditLogEntryEvent
+from sentry.constants import PROTECTED_TAG_KEYS
+from sentry.models import AuditLogEntryEvent, Environment
-class ProjectTagKeyDetailsEndpoint(ProjectEndpoint):
+class ProjectTagKeyDetailsEndpoint(ProjectEndpoint, EnvironmentMixin):
def get(self, request, project, key):
lookup_key = tagstore.prefix_reserved_key(key)
try:
- tagkey = tagstore.get_tag_key(project.id, lookup_key)
+ environment_id = self._get_environment_id_from_request(request, project.organization_id)
+ except Environment.DoesNotExist:
+ # if the environment doesn't exist then the tag can't possibly exist
+ raise ResourceDoesNotExist
+
+ try:
+ tagkey = tagstore.get_tag_key(project.id, environment_id, lookup_key)
except tagstore.TagKeyNotFound:
raise ResourceDoesNotExist
@@ -27,14 +35,17 @@ def delete(self, request, project, key):
{method} {path}
"""
+ if key in PROTECTED_TAG_KEYS:
+ return Response(status=403)
+
lookup_key = tagstore.prefix_reserved_key(key)
try:
- updated, tagkey = tagstore.delete_tag_key(project.id, lookup_key)
+ deleted = tagstore.delete_tag_key(project.id, lookup_key)
except tagstore.TagKeyNotFound:
raise ResourceDoesNotExist
- if updated:
+ for tagkey in deleted:
self.create_audit_entry(
request=request,
organization=project.organization,
diff --git a/src/sentry/api/endpoints/project_tagkey_values.py b/src/sentry/api/endpoints/project_tagkey_values.py
index e4921ed9947646..6595a20a3c2a7d 100644
--- a/src/sentry/api/endpoints/project_tagkey_values.py
+++ b/src/sentry/api/endpoints/project_tagkey_values.py
@@ -1,14 +1,15 @@
from __future__ import absolute_import
from sentry import tagstore
-from sentry.api.base import DocSection
+from sentry.api.base import DocSection, EnvironmentMixin
from sentry.api.bases.project import ProjectEndpoint
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.api.paginator import DateTimePaginator
from sentry.api.serializers import serialize
+from sentry.models import Environment
-class ProjectTagKeyValuesEndpoint(ProjectEndpoint):
+class ProjectTagKeyValuesEndpoint(ProjectEndpoint, EnvironmentMixin):
doc_section = DocSection.PROJECTS
def get(self, request, project, key):
@@ -28,11 +29,22 @@ def get(self, request, project, key):
lookup_key = tagstore.prefix_reserved_key(key)
try:
- tagkey = tagstore.get_tag_key(project.id, lookup_key)
+ environment_id = self._get_environment_id_from_request(request, project.organization_id)
+ except Environment.DoesNotExist:
+ # if the environment doesn't exist then the tag can't possibly exist
+ raise ResourceDoesNotExist
+
+ try:
+ tagkey = tagstore.get_tag_key(project.id, environment_id, lookup_key)
except tagstore.TagKeyNotFound:
raise ResourceDoesNotExist
- queryset = tagstore.get_tag_value_qs(project.id, tagkey.key, query=request.GET.get('query'))
+ queryset = tagstore.get_tag_value_qs(
+ project.id,
+ environment_id,
+ tagkey.key,
+ query=request.GET.get('query'),
+ )
return self.paginate(
request=request,
diff --git a/src/sentry/api/endpoints/project_tags.py b/src/sentry/api/endpoints/project_tags.py
index 64bcd0bf6352a0..2c809279eff7b2 100644
--- a/src/sentry/api/endpoints/project_tags.py
+++ b/src/sentry/api/endpoints/project_tags.py
@@ -5,12 +5,25 @@
from rest_framework.response import Response
from sentry import tagstore
+from sentry.api.base import EnvironmentMixin
from sentry.api.bases.project import ProjectEndpoint
+from sentry.constants import PROTECTED_TAG_KEYS
+from sentry.models import Environment
-class ProjectTagsEndpoint(ProjectEndpoint):
+class ProjectTagsEndpoint(ProjectEndpoint, EnvironmentMixin):
def get(self, request, project):
- tag_keys = tagstore.get_tag_keys(project.id)
+ try:
+ environment_id = self._get_environment_id_from_request(request, project.organization_id)
+ except Environment.DoesNotExist:
+ tag_keys = []
+ else:
+ tag_keys = sorted(
+ tagstore.get_tag_keys(
+ project.id,
+ environment_id,
+ ),
+ key=lambda x: x.key)
data = []
for tag_key in tag_keys:
@@ -18,8 +31,9 @@ def get(self, request, project):
{
'id': six.text_type(tag_key.id),
'key': tagstore.get_standardized_key(tag_key.key),
- 'name': tag_key.get_label(),
+ 'name': tagstore.get_tag_key_label(tag_key.key),
'uniqueValues': tag_key.values_seen,
+ 'canDelete': tag_key.key not in PROTECTED_TAG_KEYS,
}
)
diff --git a/src/sentry/api/endpoints/project_team_details.py b/src/sentry/api/endpoints/project_team_details.py
new file mode 100644
index 00000000000000..2cabd8ec8adc59
--- /dev/null
+++ b/src/sentry/api/endpoints/project_team_details.py
@@ -0,0 +1,84 @@
+from __future__ import absolute_import
+
+from django.http import Http404
+from rest_framework.response import Response
+
+from sentry.api.bases.project import ProjectEndpoint, ProjectPermission
+from sentry.api.serializers import serialize
+from sentry.api.serializers.models.project import ProjectWithTeamSerializer
+from sentry.models import Team
+
+
+class ProjectTeamsPermission(ProjectPermission):
+ scope_map = {
+ 'GET': ['project:read', 'project:write', 'project:admin'],
+ 'POST': ['project:write', 'project:admin'],
+ 'PUT': ['project:write', 'project:admin'],
+ # allow deletes with write permission because it's just removing
+ # a team from a project and not anything more destructive
+ 'DELETE': ['project:write', 'project:admin'],
+ }
+
+
+class ProjectTeamDetailsEndpoint(ProjectEndpoint):
+ permission_classes = (ProjectTeamsPermission, )
+
+ def post(self, request, project, team_slug):
+ """
+ Give a team access to a project
+ ```````````````````````````````
+ :pparam string organization_slug: the slug of the organization.
+ :pparam string project_slug: the slug of the project.
+ :pparam string team_slug: the slug of the project.
+ :auth: required
+ """
+ try:
+ team = Team.objects.get(
+ organization_id=project.organization_id,
+ slug=team_slug,
+ )
+ except Team.DoesNotExist:
+ raise Http404
+ if not request.access.has_team_scope(team, 'project:write'):
+ return Response(
+ {
+ 'detail': ['You do not have permission to perform this action.']
+ },
+ status=403
+ )
+ project.add_team(team)
+ return Response(
+ serialize(project, request.user, ProjectWithTeamSerializer()),
+ status=201,
+ )
+
+ def delete(self, request, project, team_slug):
+ """
+ Revoke a team's access to a project
+ ```````````````````````````````````
+ :pparam string organization_slug: the slug of the organization.
+ :pparam string project_slug: the slug of the project.
+ :pparam string team_slug: the slug of the project.
+ :auth: required
+ """
+ try:
+ team = Team.objects.get(
+ organization_id=project.organization_id,
+ slug=team_slug,
+ )
+ except Team.DoesNotExist:
+ raise Http404
+
+ if not request.access.has_team_scope(team, 'project:write'):
+ return Response(
+ {
+ 'detail': ['You do not have permission to perform this action.']
+ },
+ status=403
+ )
+ project.remove_team(team)
+
+ return Response(
+ serialize(project, request.user, ProjectWithTeamSerializer()),
+ status=200,
+ )
diff --git a/src/sentry/api/endpoints/project_teams.py b/src/sentry/api/endpoints/project_teams.py
new file mode 100644
index 00000000000000..8ed3e55e8b53c2
--- /dev/null
+++ b/src/sentry/api/endpoints/project_teams.py
@@ -0,0 +1,32 @@
+from __future__ import absolute_import
+
+from sentry.api.bases.project import ProjectEndpoint
+from sentry.api.paginator import OffsetPaginator
+from sentry.api.serializers import serialize
+from sentry.models import Team
+
+
+class ProjectTeamsEndpoint(ProjectEndpoint):
+
+ def get(self, request, project):
+ """
+ List a Project's Teams
+ ``````````````````````
+
+ Return a list of teams that have access to this project.
+
+ :pparam string organization_slug: the slug of the organization.
+ :pparam string project_slug: the slug of the project.
+ :auth: required
+ """
+ queryset = Team.objects.filter(
+ projectteam__project=project,
+ )
+
+ return self.paginate(
+ request=request,
+ queryset=queryset,
+ order_by='name',
+ paginator_cls=OffsetPaginator,
+ on_results=lambda x: serialize(x, request.user),
+ )
diff --git a/src/sentry/api/endpoints/project_user_reports.py b/src/sentry/api/endpoints/project_user_reports.py
index 9fd5184b74cc74..678f93e78af592 100644
--- a/src/sentry/api/endpoints/project_user_reports.py
+++ b/src/sentry/api/endpoints/project_user_reports.py
@@ -6,11 +6,12 @@
from rest_framework.response import Response
from uuid import uuid4
-from sentry.api.base import DocSection
+from sentry.api.base import DocSection, EnvironmentMixin
from sentry.api.bases.project import ProjectEndpoint
from sentry.api.serializers import serialize, ProjectUserReportSerializer
from sentry.api.paginator import DateTimePaginator
-from sentry.models import (Event, EventMapping, EventUser, Group, GroupStatus, UserReport)
+from sentry.models import (Event, EventUser, Group, GroupStatus, UserReport)
+from sentry.signals import user_feedback_received
from sentry.utils.apidocs import scenario, attach_scenarios
@@ -35,7 +36,7 @@ class Meta:
fields = ('name', 'email', 'comments', 'event_id')
-class ProjectUserReportsEndpoint(ProjectEndpoint):
+class ProjectUserReportsEndpoint(ProjectEndpoint, EnvironmentMixin):
doc_section = DocSection.PROJECTS
def get(self, request, project):
@@ -66,7 +67,10 @@ def get(self, request, project):
request=request,
queryset=queryset,
order_by='-date_added',
- on_results=lambda x: serialize(x, request.user, ProjectUserReportSerializer()),
+ on_results=lambda x: serialize(x, request.user, ProjectUserReportSerializer(
+ environment_func=self._get_environment_func(
+ request, project.organization_id)
+ )),
paginator_cls=DateTimePaginator,
)
@@ -102,15 +106,9 @@ def post(self, request, project):
report.event_user_id = euser.id
try:
- mapping = EventMapping.objects.get(
- event_id=report.event_id,
- project_id=project.id,
- )
- except EventMapping.DoesNotExist:
- # XXX(dcramer): the system should fill this in later
+ report.group = Group.objects.from_event_id(project, report.event_id)
+ except Group.DoesNotExist:
pass
- else:
- report.group = Group.objects.get(id=mapping.group_id)
try:
with transaction.atomic():
@@ -136,7 +134,16 @@ def post(self, request, project):
)
report = existing_report
- return Response(serialize(report, request.user, ProjectUserReportSerializer()))
+ else:
+ if report.group:
+ report.notify()
+
+ user_feedback_received.send(project=report.project, group=report.group, sender=self)
+
+ return Response(serialize(report, request.user, ProjectUserReportSerializer(
+ environment_func=self._get_environment_func(
+ request, project.organization_id)
+ )))
def find_event_user(self, report):
try:
diff --git a/src/sentry/api/endpoints/project_user_stats.py b/src/sentry/api/endpoints/project_user_stats.py
index 7bae8bcc710023..4fab96f8395889 100644
--- a/src/sentry/api/endpoints/project_user_stats.py
+++ b/src/sentry/api/endpoints/project_user_stats.py
@@ -5,21 +5,32 @@
from rest_framework.response import Response
from sentry.app import tsdb
+from sentry.api.base import EnvironmentMixin
from sentry.api.bases.project import ProjectEndpoint
+from sentry.api.exceptions import ResourceDoesNotExist
+from sentry.models import Environment
-class ProjectUserStatsEndpoint(ProjectEndpoint):
+class ProjectUserStatsEndpoint(EnvironmentMixin, ProjectEndpoint):
def get(self, request, project):
+ try:
+ environment_id = self._get_environment_id_from_request(
+ request,
+ project.organization_id,
+ )
+ except Environment.DoesNotExist:
+ raise ResourceDoesNotExist
+
now = timezone.now()
then = now - timedelta(days=30)
- results = tsdb.rollup(
- tsdb.get_distinct_counts_series(
- tsdb.models.users_affected_by_project,
- (project.id, ),
- then,
- now,
- ), 3600 * 24
+ results = tsdb.get_distinct_counts_series(
+ tsdb.models.users_affected_by_project,
+ (project.id, ),
+ then,
+ now,
+ rollup=3600 * 24,
+ environment_id=environment_id,
)[project.id]
return Response(results)
diff --git a/src/sentry/api/endpoints/setup_wizard.py b/src/sentry/api/endpoints/setup_wizard.py
new file mode 100644
index 00000000000000..197ca16ccf5315
--- /dev/null
+++ b/src/sentry/api/endpoints/setup_wizard.py
@@ -0,0 +1,63 @@
+from __future__ import absolute_import
+import logging
+
+from rest_framework.response import Response
+
+from sentry import ratelimits
+from sentry.cache import default_cache
+from sentry.api.base import Endpoint
+from sentry.api.serializers import serialize
+from django.utils.crypto import get_random_string
+
+logger = logging.getLogger('sentry.api')
+SETUP_WIZARD_CACHE_KEY = 'setup-wizard-keys:v1:'
+SETUP_WIZARD_CACHE_TIMEOUT = 600
+
+
+class SetupWizard(Endpoint):
+ permission_classes = ()
+
+ def delete(self, request, wizard_hash=None):
+ """
+ This removes the cache content for a specific hash
+ """
+ if wizard_hash is not None:
+ key = '%s%s' % (SETUP_WIZARD_CACHE_KEY, wizard_hash)
+ default_cache.delete(key)
+ return Response(status=200)
+
+ def get(self, request, wizard_hash=None):
+ """
+ This tries to retrieve and return the cache content if possible
+ otherwise creates new cache
+ """
+ if wizard_hash is not None:
+ key = '%s%s' % (SETUP_WIZARD_CACHE_KEY, wizard_hash)
+ wizard_data = default_cache.get(key)
+
+ if wizard_data is None:
+ return Response(status=404)
+ elif wizard_data == 'empty':
+ # when we just created a clean cache
+ return Response(status=400)
+
+ return Response(serialize(wizard_data))
+ else:
+ # This creates a new available hash url for the project wizard
+ rate_limited = ratelimits.is_limited(
+ key='rl:setup-wizard:ip:%s' % request.META['REMOTE_ADDR'],
+ limit=10,
+ )
+ if rate_limited:
+ logger.info('setup-wizard.rate-limit')
+ return Response(
+ {
+ 'Too wizard requests',
+ }, status=403
+ )
+ wizard_hash = get_random_string(
+ 64, allowed_chars='abcdefghijklmnopqrstuvwxyz012345679')
+
+ key = '%s%s' % (SETUP_WIZARD_CACHE_KEY, wizard_hash)
+ default_cache.set(key, 'empty', SETUP_WIZARD_CACHE_TIMEOUT)
+ return Response(serialize({'hash': wizard_hash}))
diff --git a/src/sentry/api/endpoints/shared_group_details.py b/src/sentry/api/endpoints/shared_group_details.py
index 538a8c94450aee..44512ceae15610 100644
--- a/src/sentry/api/endpoints/shared_group_details.py
+++ b/src/sentry/api/endpoints/shared_group_details.py
@@ -2,7 +2,7 @@
from rest_framework.response import Response
-from sentry.api.base import Endpoint
+from sentry.api.base import Endpoint, EnvironmentMixin
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.api.serializers import (
serialize, SharedEventSerializer, SharedGroupSerializer, SharedProjectSerializer
@@ -10,7 +10,7 @@
from sentry.models import Group
-class SharedGroupDetailsEndpoint(Endpoint):
+class SharedGroupDetailsEndpoint(Endpoint, EnvironmentMixin):
permission_classes = ()
def get(self, request, share_id):
@@ -36,7 +36,14 @@ def get(self, request, share_id):
event = group.get_latest_event()
- context = serialize(group, request.user, SharedGroupSerializer())
+ context = serialize(
+ group,
+ request.user,
+ SharedGroupSerializer(
+ environment_func=self._get_environment_func(
+ request, group.project.organization_id)
+ )
+ )
# TODO(dcramer): move latestEvent/project into SharedGroupSerializer
context['latestEvent'] = serialize(event, request.user, SharedEventSerializer())
context['project'] = serialize(group.project, request.user, SharedProjectSerializer())
diff --git a/src/sentry/api/endpoints/sudo.py b/src/sentry/api/endpoints/sudo.py
new file mode 100644
index 00000000000000..8c4a1c1a653e19
--- /dev/null
+++ b/src/sentry/api/endpoints/sudo.py
@@ -0,0 +1,44 @@
+from __future__ import absolute_import
+
+from django.contrib import auth
+from rest_framework import status
+from rest_framework.response import Response
+from rest_framework.permissions import IsAuthenticated
+from sudo.utils import grant_sudo_privileges
+
+from sentry.api.base import Endpoint
+from sentry.models import Authenticator
+from sentry.utils import json
+
+
+class SudoEndpoint(Endpoint):
+ permission_classes = (IsAuthenticated, )
+
+ def post(self, request):
+ authenticated = False
+
+ if 'challenge' in request.DATA and 'response' in request.DATA:
+ try:
+ interface = Authenticator.objects.get_interface(request.user, 'u2f')
+ if not interface.is_enrolled:
+ raise LookupError()
+
+ challenge = json.loads(request.DATA['challenge'])
+ response = json.loads(request.DATA['response'])
+ authenticated = interface.validate_response(request, challenge, response)
+ except ValueError:
+ pass
+ except LookupError:
+ pass
+
+ else:
+ authenticated = auth.authenticate(
+ username=request.user.email,
+ password=request.DATA.get('password'))
+
+ if authenticated:
+ grant_sudo_privileges(request._request)
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+ return Response({'allowFail': True}, content_type="application/json",
+ status=status.HTTP_401_UNAUTHORIZED)
diff --git a/src/sentry/api/endpoints/team_groups_new.py b/src/sentry/api/endpoints/team_groups_new.py
index 52480c2c484a6e..affbc976163a4e 100644
--- a/src/sentry/api/endpoints/team_groups_new.py
+++ b/src/sentry/api/endpoints/team_groups_new.py
@@ -4,12 +4,13 @@
from django.utils import timezone
from rest_framework.response import Response
+from sentry.api.base import EnvironmentMixin
from sentry.api.bases.team import TeamEndpoint
-from sentry.api.serializers import serialize
+from sentry.api.serializers import serialize, GroupSerializer
from sentry.models import Group, GroupStatus, Project
-class TeamGroupsNewEndpoint(TeamEndpoint):
+class TeamGroupsNewEndpoint(TeamEndpoint, EnvironmentMixin):
def get(self, request, team):
"""
Return a list of the newest groups for a given team.
@@ -41,4 +42,12 @@ def get(self, request, team):
for group in group_list:
group._project_cache = project_dict.get(group.project_id)
- return Response(serialize(group_list, request.user))
+ return Response(
+ serialize(
+ group_list,
+ request.user,
+ GroupSerializer(
+ environment_func=self._get_environment_func(request, team.organization_id)
+ )
+ )
+ )
diff --git a/src/sentry/api/endpoints/team_groups_trending.py b/src/sentry/api/endpoints/team_groups_trending.py
index aacef7dab532f3..d17473aa318dab 100644
--- a/src/sentry/api/endpoints/team_groups_trending.py
+++ b/src/sentry/api/endpoints/team_groups_trending.py
@@ -4,12 +4,13 @@
from django.utils import timezone
from rest_framework.response import Response
+from sentry.api.base import EnvironmentMixin
from sentry.api.bases.team import TeamEndpoint
-from sentry.api.serializers import serialize
+from sentry.api.serializers import serialize, GroupSerializer
from sentry.models import Group, GroupStatus, Project
-class TeamGroupsTrendingEndpoint(TeamEndpoint):
+class TeamGroupsTrendingEndpoint(TeamEndpoint, EnvironmentMixin):
def get(self, request, team):
"""
Return a list of the trending groups for a given team.
@@ -41,4 +42,13 @@ def get(self, request, team):
for group in group_list:
group._project_cache = project_dict.get(group.project_id)
- return Response(serialize(group_list, request.user))
+ return Response(
+ serialize(
+ group_list,
+ request.user,
+ GroupSerializer(
+ environment_func=self._get_environment_func(
+ request, team.organization_id)
+ )
+ )
+ )
diff --git a/src/sentry/api/endpoints/team_project_index.py b/src/sentry/api/endpoints/team_project_index.py
index a679563e5cca56..2dea644900abea 100644
--- a/src/sentry/api/endpoints/team_project_index.py
+++ b/src/sentry/api/endpoints/team_project_index.py
@@ -73,7 +73,7 @@ def get(self, request, team):
else:
# TODO(dcramer): status should be selectable
results = list(Project.objects.filter(
- team=team,
+ teams=team,
status=ProjectStatus.VISIBLE,
))
diff --git a/src/sentry/api/endpoints/team_stats.py b/src/sentry/api/endpoints/team_stats.py
index 831824ea4ca55a..b79582fcfd4e89 100644
--- a/src/sentry/api/endpoints/team_stats.py
+++ b/src/sentry/api/endpoints/team_stats.py
@@ -4,9 +4,10 @@
from six.moves import range
from sentry import tsdb
-from sentry.api.base import DocSection, StatsMixin
+from sentry.api.base import DocSection, EnvironmentMixin, StatsMixin
from sentry.api.bases.team import TeamEndpoint
-from sentry.models import Project
+from sentry.api.exceptions import ResourceDoesNotExist
+from sentry.models import Environment, Project
from sentry.utils.apidocs import scenario, attach_scenarios
@@ -17,7 +18,7 @@ def retrieve_event_counts_team(runner):
)
-class TeamStatsEndpoint(TeamEndpoint, StatsMixin):
+class TeamStatsEndpoint(TeamEndpoint, EnvironmentMixin, StatsMixin):
doc_section = DocSection.TEAMS
@attach_scenarios([retrieve_event_counts_team])
@@ -50,6 +51,14 @@ def get(self, request, team):
values.
:auth: required
"""
+ try:
+ environment_id = self._get_environment_id_from_request(
+ request,
+ team.organization_id,
+ )
+ except Environment.DoesNotExist:
+ raise ResourceDoesNotExist
+
projects = Project.objects.get_for_user(
team=team,
user=request.user,
@@ -62,7 +71,7 @@ def get(self, request, team):
tsdb.get_range(
model=tsdb.models.project,
keys=[p.id for p in projects],
- **self._parse_args(request)
+ **self._parse_args(request, environment_id)
).values()
)
diff --git a/src/sentry/api/endpoints/user_appearance.py b/src/sentry/api/endpoints/user_appearance.py
new file mode 100644
index 00000000000000..ad5ce0ca8ae953
--- /dev/null
+++ b/src/sentry/api/endpoints/user_appearance.py
@@ -0,0 +1,97 @@
+from __future__ import absolute_import
+
+from datetime import datetime
+
+import pytz
+from django.conf import settings
+from rest_framework.response import Response
+from rest_framework import serializers
+from django.utils.translation import ugettext_lazy as _
+
+from sentry.api.bases.user import UserEndpoint
+from sentry.constants import LANGUAGES
+from sentry.models import UserOption
+
+
+def _get_timezone_choices():
+ results = []
+ for tz in pytz.common_timezones:
+ now = datetime.now(pytz.timezone(tz))
+ offset = now.strftime('%z')
+ results.append((int(offset), tz, '(UTC%s) %s' % (offset, tz)))
+ results.sort()
+
+ for i in range(len(results)):
+ results[i] = results[i][1:]
+ return results
+
+
+TIMEZONE_CHOICES = _get_timezone_choices()
+
+
+class UserAppearanceSerializer(serializers.Serializer):
+ # Note the label part of these ChoiceFields are not used by the frontend
+ language = serializers.ChoiceField(choices=LANGUAGES, required=False)
+ stacktraceOrder = serializers.ChoiceField(choices=(
+ ('-1', _('Default (let Sentry decide)')),
+ ('1', _('Most recent call last')),
+ ('2', _('Most recent call first')),
+ ), required=False)
+ timezone = serializers.ChoiceField(choices=TIMEZONE_CHOICES, required=False)
+ clock24Hours = serializers.BooleanField(required=False)
+
+
+class UserAppearanceEndpoint(UserEndpoint):
+ def get(self, request, user):
+ """
+ Retrieve Account "Appearance" options
+ `````````````````````````````````````
+
+ Return details for an account's appearance options such as: timezone, 24hr times, language,
+ stacktrace_order.
+
+ :auth: required
+ """
+ options = UserOption.objects.get_all_values(user=user, project=None)
+
+ return Response({
+ 'language': options.get('language') or request.LANGUAGE_CODE,
+ 'stacktraceOrder': int(options.get('stacktrace_order', -1) or -1),
+ 'timezone': options.get('timezone') or settings.SENTRY_DEFAULT_TIME_ZONE,
+ 'clock24Hours': options.get('clock_24_hours') or False,
+ })
+
+ def put(self, request, user):
+ """
+ Update Account Appearance options
+ `````````````````````````````````
+
+ Update account appearance options. Only supplied values are updated.
+
+ :param string language: language preference
+ :param string stacktrace_order: One of -1 (default), 1 (most recent call last), 2 (most recent call first).
+ :param string timezone: timezone option
+ :param clock_24_hours boolean: use 24 hour clock
+ :auth: required
+ """
+ serializer = UserAppearanceSerializer(data=request.DATA, partial=True)
+
+ if not serializer.is_valid():
+ return Response(serializer.errors, status=400)
+
+ result = serializer.object
+
+ # map API keys to keys in model
+ key_map = {
+ 'stacktraceOrder': 'stacktrace_order',
+ 'clock24Hours': 'clock_24_hours',
+ }
+
+ for key in result:
+ UserOption.objects.set_value(
+ user=user,
+ key=key_map.get(key, key),
+ value=result.get(key),
+ )
+
+ return Response(status=204)
diff --git a/src/sentry/api/endpoints/user_details.py b/src/sentry/api/endpoints/user_details.py
index 17b4be4f12dada..2ce464037ca826 100644
--- a/src/sentry/api/endpoints/user_details.py
+++ b/src/sentry/api/endpoints/user_details.py
@@ -1,13 +1,56 @@
from __future__ import absolute_import
+from datetime import datetime
+
+import pytz
+import logging
+
from django.conf import settings
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.auth import logout
from rest_framework import serializers, status
from rest_framework.response import Response
+from sentry import roles
+from sentry.api import client
from sentry.api.bases.user import UserEndpoint
+from sentry.api.decorators import sudo_required
from sentry.api.serializers import serialize
from sentry.api.serializers.models.user import DetailedUserSerializer
-from sentry.models import User, UserOption
+from sentry.api.serializers.rest_framework import ListField
+from sentry.auth.superuser import is_active_superuser
+from sentry.constants import LANGUAGES
+from sentry.models import Organization, OrganizationMember, OrganizationStatus, User, UserOption
+
+delete_logger = logging.getLogger('sentry.deletions.ui')
+
+
+def _get_timezone_choices():
+ results = []
+ for tz in pytz.common_timezones:
+ now = datetime.now(pytz.timezone(tz))
+ offset = now.strftime('%z')
+ results.append((int(offset), tz, '(UTC%s) %s' % (offset, tz)))
+ results.sort()
+
+ for i in range(len(results)):
+ results[i] = results[i][1:]
+ return results
+
+
+TIMEZONE_CHOICES = _get_timezone_choices()
+
+
+class UserOptionsSerializer(serializers.Serializer):
+ language = serializers.ChoiceField(choices=LANGUAGES, required=False)
+ stacktraceOrder = serializers.ChoiceField(choices=(
+ ('-1', _('Default (let Sentry decide)')),
+ ('1', _('Most recent call last')),
+ ('2', _('Most recent call first')),
+ ), required=False)
+ timezone = serializers.ChoiceField(choices=TIMEZONE_CHOICES, required=False)
+ clock24Hours = serializers.BooleanField(required=False)
+ seenReleaseBroadcast = serializers.BooleanField(required=False)
class BaseUserSerializer(serializers.ModelSerializer):
@@ -22,6 +65,7 @@ def validate(self, attrs):
if self.object.email == self.object.username:
if attrs.get('username', self.object.email) != self.object.email:
+ # ... this probably needs to handle newsletters and such?
attrs.setdefault('email', attrs['username'])
return attrs
@@ -35,21 +79,13 @@ def restore_object(self, attrs, instance=None):
class UserSerializer(BaseUserSerializer):
class Meta:
model = User
- fields = ('name', 'username', 'email')
-
- def validate_username(self, attrs, source):
- value = attrs[source]
- if User.objects.filter(username__iexact=value).exclude(id=self.object.id).exists():
- raise serializers.ValidationError('That username is already in use.')
- return attrs
+ fields = ('name', 'username')
def validate(self, attrs):
for field in settings.SENTRY_MANAGED_USER_FIELDS:
attrs.pop(field, None)
- attrs = super(UserSerializer, self).validate(attrs)
-
- return attrs
+ return super(UserSerializer, self).validate(attrs)
class AdminUserSerializer(BaseUserSerializer):
@@ -59,32 +95,145 @@ class Meta:
model = User
# no idea wtf is up with django rest framework, but we need is_active
# and isActive
- fields = ('name', 'username', 'isActive', 'email')
+ fields = ('name', 'username', 'isActive')
# write_only_fields = ('password',)
+class OrganizationsSerializer(serializers.Serializer):
+ organizations = ListField(child=serializers.CharField(), required=True)
+
+
class UserDetailsEndpoint(UserEndpoint):
def get(self, request, user):
- data = serialize(user, request.user, DetailedUserSerializer())
- return Response(data)
+ """
+ Retrieve User Details
+ `````````````````````
+
+ Return details for an account's details and options such as: full name, timezone, 24hr times, language,
+ stacktrace_order.
+
+ :auth: required
+ """
+ return Response(serialize(user, request.user, DetailedUserSerializer()))
def put(self, request, user):
- if request.is_superuser():
+ """
+ Update Account Appearance options
+ `````````````````````````````````
+
+ Update account appearance options. Only supplied values are updated.
+
+ :pparam string user_id: user id
+ :param string language: language preference
+ :param string stacktrace_order: One of -1 (default), 1 (most recent call last), 2 (most recent call first).
+ :param string timezone: timezone option
+ :param clock_24_hours boolean: use 24 hour clock
+ :auth: required
+ """
+
+ if is_active_superuser(request):
serializer_cls = AdminUserSerializer
else:
serializer_cls = UserSerializer
serializer = serializer_cls(user, data=request.DATA, partial=True)
- if serializer.is_valid():
- user = serializer.save()
+ serializer_options = UserOptionsSerializer(
+ data=request.DATA.get('options', {}), partial=True)
+
+ # This serializer should NOT include privileged fields e.g. password
+ if not serializer.is_valid() or not serializer_options.is_valid():
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ # map API keys to keys in model
+ key_map = {
+ 'language': 'language',
+ 'timezone': 'timezone',
+ 'stacktraceOrder': 'stacktrace_order',
+ 'clock24Hours': 'clock_24_hours',
+ 'seenReleaseBroadcast': 'seen_release_broadcast',
+ }
+
+ options_result = serializer_options.object
- options = request.DATA.get('options', {})
- if options.get('seenReleaseBroadcast'):
+ for key in key_map:
+ if key in options_result:
UserOption.objects.set_value(
user=user,
- key='seen_release_broadcast',
- value=options.get('seenReleaseBroadcast'),
+ key=key_map.get(key, key),
+ value=options_result.get(key),
)
- return Response(serialize(user, request.user))
- return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+ user = serializer.save()
+
+ return Response(serialize(user, request.user, DetailedUserSerializer()))
+
+ @sudo_required
+ def delete(self, request, user):
+ """
+ Delete User Account
+
+ Also removes organizations if they are an owner
+ :pparam string user_id: user id
+ :param list organizations: List of organization ids to remove
+ :auth required:
+ """
+
+ serializer = OrganizationsSerializer(data=request.DATA)
+
+ if not serializer.is_valid():
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+
+ # from `frontend/remove_account.py`
+ org_list = Organization.objects.filter(
+ member_set__role__in=[x.id for x in roles.with_scope('org:admin')],
+ member_set__user=user,
+ status=OrganizationStatus.VISIBLE,
+ )
+
+ org_results = []
+ for org in org_list:
+ org_results.append({
+ 'organization': org,
+ 'single_owner': org.has_single_owner(),
+ })
+
+ avail_org_slugs = set([o['organization'].slug for o in org_results])
+ orgs_to_remove = set(serializer.object.get('organizations')).intersection(avail_org_slugs)
+
+ for result in org_results:
+ if result['single_owner']:
+ orgs_to_remove.add(result['organization'].slug)
+
+ delete_logger.info(
+ 'user.deactivate',
+ extra={
+ 'actor_id': request.user.id,
+ 'ip_address': request.META['REMOTE_ADDR'],
+ }
+ )
+
+ for org_slug in orgs_to_remove:
+ client.delete(
+ path='/organizations/{}/'.format(org_slug),
+ request=request,
+ is_sudo=True)
+
+ remaining_org_ids = [
+ o.id for o in org_list if o.slug in avail_org_slugs.difference(orgs_to_remove)
+ ]
+
+ if remaining_org_ids:
+ OrganizationMember.objects.filter(
+ organization__in=remaining_org_ids,
+ user=request.user,
+ ).delete()
+
+ User.objects.filter(
+ id=request.user.id,
+ ).update(
+ is_active=False,
+ )
+
+ logout(request)
+
+ return Response(status=status.HTTP_204_NO_CONTENT)
diff --git a/src/sentry/api/endpoints/user_emails.py b/src/sentry/api/endpoints/user_emails.py
new file mode 100644
index 00000000000000..a2f694babf5603
--- /dev/null
+++ b/src/sentry/api/endpoints/user_emails.py
@@ -0,0 +1,240 @@
+from __future__ import absolute_import
+
+import logging
+
+from django.db import IntegrityError, transaction
+from django.db.models import Q
+from rest_framework import serializers, status
+from rest_framework.response import Response
+
+from sentry import newsletter
+from sentry.api.bases.user import UserEndpoint
+from sentry.api.decorators import sudo_required
+from sentry.api.serializers import serialize
+from sentry.models import User, UserEmail, UserOption
+
+logger = logging.getLogger('sentry.accounts')
+
+
+class InvalidEmailResponse(Response):
+ def __init__(self):
+ super(InvalidEmailResponse, self).__init__(
+ {'detail': 'Invalid email'},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+
+class InvalidEmailError(Exception):
+ pass
+
+
+class DuplicateEmailError(Exception):
+ pass
+
+
+class EmailSerializer(serializers.Serializer):
+ email = serializers.EmailField(required=True)
+ newsletter = serializers.BooleanField(required=False, default=False)
+
+
+def add_email(email, user, subscribe_newsletter=False):
+ """
+ Adds an email to user account
+
+ Can be either primary or secondary
+ """
+
+ # Bad email
+ if email is None:
+ raise InvalidEmailError
+
+ try:
+ with transaction.atomic():
+ new_email = UserEmail.objects.create(user=user, email=email)
+ except IntegrityError:
+ raise DuplicateEmailError
+ else:
+ new_email.set_hash()
+ new_email.save()
+ user.send_confirm_email_singular(new_email)
+
+ # Update newsletter subscription and mark as unverified
+ if subscribe_newsletter:
+ newsletter.update_subscription(user=user,
+ verified=False,
+ list_id=1,
+ )
+ return new_email
+
+
+class UserEmailsEndpoint(UserEndpoint):
+ def get(self, request, user):
+ """
+ Get list of emails
+ ``````````````````
+
+ Returns a list of emails. Primary email will have `isPrimary: true`
+
+ :auth required:
+ """
+
+ emails = user.emails.all()
+
+ return Response(serialize(list(emails), user=user))
+
+ @sudo_required
+ def post(self, request, user):
+ """
+ Adds a secondary email address
+ ``````````````````````````````
+
+ Adds a secondary email address to account.
+
+ :param string email: email to add
+ :auth required:
+ """
+
+ serializer = EmailSerializer(data=request.DATA)
+
+ if not serializer.is_valid():
+ return InvalidEmailResponse()
+
+ try:
+ new_email = add_email(
+ serializer.object['email'].lower().strip(),
+ user,
+ serializer.object['newsletter'])
+ except (InvalidEmailError, DuplicateEmailError):
+ return InvalidEmailResponse()
+ else:
+ logger.info(
+ 'user.email.add',
+ extra={
+ 'user_id': user.id,
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'email': new_email.email,
+ }
+ )
+
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+ @sudo_required
+ def put(self, request, user):
+ """
+ Updates primary email
+ `````````````````````
+
+ Changes primary email
+
+ :param string email: the email to set as primary email
+ :auth required:
+ """
+
+ serializer = EmailSerializer(data=request.DATA)
+
+ if not serializer.is_valid():
+ return InvalidEmailResponse()
+
+ old_email = user.email
+
+ if not serializer.is_valid():
+ return InvalidEmailResponse()
+
+ new_email = serializer.object['email'].lower().strip()
+
+ # If email doesn't exist for user, attempt to add new email
+ if not UserEmail.objects.filter(
+ user=user, email__iexact=new_email
+ ).exists():
+ try:
+ added_email = add_email(new_email, user, serializer.object['newsletter'])
+ except InvalidEmailError:
+ return InvalidEmailResponse()
+ except DuplicateEmailError:
+ pass
+ else:
+ logger.info(
+ 'user.email.add',
+ extra={
+ 'user_id': user.id,
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'email': added_email.email,
+ }
+ )
+ new_email = added_email.email
+
+ # Check if email is in use
+ # Is this a security/abuse concern?
+ if User.objects.filter(Q(email__iexact=new_email) | Q(username__iexact=new_email)
+ ).exclude(id=user.id).exists():
+ return InvalidEmailResponse()
+
+ if new_email == old_email:
+ return InvalidEmailResponse()
+
+ # update notification settings for those set to primary email with new primary email
+ alert_email = UserOption.objects.get_value(user=user, key='alert_email')
+ if alert_email == old_email:
+ UserOption.objects.set_value(user=user, key='alert_email', value=new_email)
+
+ options = UserOption.objects.filter(user=user, key='mail:email')
+ for option in options:
+ if option.value != old_email:
+ continue
+ option.update(value=new_email)
+
+ has_new_username = old_email == user.username
+
+ update_kwargs = {
+ 'email': new_email,
+ }
+
+ if has_new_username and not User.objects.filter(username__iexact=new_email).exists():
+ update_kwargs['username'] = new_email
+
+ user.update(**update_kwargs)
+
+ logger.info(
+ 'user.email.edit',
+ extra={
+ 'user_id': user.id,
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'email': new_email,
+ }
+ )
+
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+ @sudo_required
+ def delete(self, request, user):
+ """
+ Removes an email from account
+ `````````````````````````````
+
+ Removes an email from account, can not remove primary email
+
+ :param string email: email to remove
+ :auth required:
+ """
+
+ email = request.DATA.get('email')
+ primary_email = UserEmail.get_primary_email(user)
+ del_email = UserEmail.objects.filter(user=user, email=email)[0]
+
+ # Don't allow deleting primary email?
+ if primary_email == del_email:
+ return Response({'detail': 'Cannot remove primary email'},
+ status=status.HTTP_400_BAD_REQUEST)
+
+ del_email.delete()
+
+ logger.info(
+ 'user.email.remove',
+ extra={
+ 'user_id': user.id,
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'email': email,
+ }
+ )
+
+ return Response(status=status.HTTP_204_NO_CONTENT)
diff --git a/src/sentry/api/endpoints/user_notification_details.py b/src/sentry/api/endpoints/user_notification_details.py
new file mode 100644
index 00000000000000..278039b81d62b3
--- /dev/null
+++ b/src/sentry/api/endpoints/user_notification_details.py
@@ -0,0 +1,101 @@
+from __future__ import absolute_import
+
+import six
+
+from collections import defaultdict
+
+from sentry.api.bases.user import UserEndpoint
+from sentry.models import UserOption, UserOptionValue
+
+from sentry.api.serializers import serialize, Serializer
+
+from rest_framework.response import Response
+
+from rest_framework import serializers
+
+USER_OPTION_SETTINGS = {
+ 'deployNotifications': {
+ 'key': 'deploy-emails',
+ 'default': UserOptionValue.committed_deploys_only, # '3'
+ 'type': int,
+ },
+ 'personalActivityNotifications': {
+ 'key': 'self_notifications',
+ 'default': UserOptionValue.all_conversations, # '0'
+ 'type': bool,
+ },
+ 'selfAssignOnResolve': {
+ 'key': 'self_assign_issue',
+ 'default': UserOptionValue.all_conversations, # '0'
+ 'type': bool,
+ },
+ 'subscribeByDefault': {
+ 'key': 'subscribe_by_default',
+ 'default': UserOptionValue.participating_only, # '1'
+ 'type': bool,
+ },
+ 'workflowNotifications': {
+ 'key': 'workflow:notifications',
+ 'default': UserOptionValue.all_conversations, # '0'
+ 'type': int,
+ }
+}
+
+
+class UserNotificationsSerializer(Serializer):
+ def get_attrs(self, item_list, user, *args, **kwargs):
+ data = list(UserOption.objects.filter(
+ user__in=item_list,
+ project=None).select_related('user'))
+
+ results = defaultdict(list)
+
+ for uo in data:
+ results[uo.user].append(uo)
+
+ return results
+
+ def serialize(self, obj, attrs, user, *args, **kwargs):
+
+ raw_data = {option.key: option.value for option in attrs}
+
+ data = {}
+ for key in USER_OPTION_SETTINGS:
+ uo = USER_OPTION_SETTINGS[key]
+ val = raw_data.get(uo['key'], uo['default'])
+ if (uo['type'] == bool):
+ data[key] = bool(int(val)) # '1' is true, '0' is false
+ elif (uo['type'] == int):
+ data[key] = int(val)
+
+ data['weeklyReports'] = True # This cannot be overridden
+
+ return data
+
+
+class UserNotificationDetailsSerializer(serializers.Serializer):
+ deployNotifications = serializers.IntegerField(required=False, min_value=2, max_value=4)
+ personalActivityNotifications = serializers.BooleanField(required=False)
+ selfAssignOnResolve = serializers.BooleanField(required=False)
+ subscribeByDefault = serializers.BooleanField(required=False)
+ workflowNotifications = serializers.IntegerField(required=False, min_value=0, max_value=2)
+
+
+class UserNotificationDetailsEndpoint(UserEndpoint):
+ def get(self, request, user):
+ serialized = serialize(user, request.user, UserNotificationsSerializer())
+ return Response(serialized)
+
+ def put(self, request, user):
+ serializer = UserNotificationDetailsSerializer(data=request.DATA)
+
+ if serializer.is_valid():
+ for key in serializer.object:
+ db_key = USER_OPTION_SETTINGS[key]['key']
+ val = six.text_type(int(serializer.object[key]))
+ (uo, created) = UserOption.objects.get_or_create(user=user, key=db_key, project=None)
+ uo.update(value=val)
+
+ return self.get(request, user)
+ else:
+ return Response(serializer.errors, status=400)
diff --git a/src/sentry/api/endpoints/user_social_identities_index.py b/src/sentry/api/endpoints/user_social_identities_index.py
new file mode 100644
index 00000000000000..4ab2b47c49b351
--- /dev/null
+++ b/src/sentry/api/endpoints/user_social_identities_index.py
@@ -0,0 +1,22 @@
+from __future__ import absolute_import
+
+from rest_framework.response import Response
+from social_auth.models import UserSocialAuth
+
+from sentry.api.bases.user import UserEndpoint
+from sentry.api.serializers import serialize
+
+
+class UserSocialIdentitiesIndexEndpoint(UserEndpoint):
+ def get(self, request, user):
+ """
+ List Account's Identities
+ `````````````````````````
+
+ List an account's associated identities (e.g. github when trying to add a repo)
+
+ :auth: required
+ """
+
+ identity_list = list(UserSocialAuth.objects.filter(user=user))
+ return Response(serialize(identity_list))
diff --git a/src/sentry/api/endpoints/user_social_identity_details.py b/src/sentry/api/endpoints/user_social_identity_details.py
new file mode 100644
index 00000000000000..19432399964bb2
--- /dev/null
+++ b/src/sentry/api/endpoints/user_social_identity_details.py
@@ -0,0 +1,62 @@
+from __future__ import absolute_import
+
+import logging
+import six
+
+from rest_framework.response import Response
+from social_auth.backends import get_backend
+from social_auth.models import UserSocialAuth
+
+from sentry.api.bases.user import UserEndpoint
+
+logger = logging.getLogger('sentry.accounts')
+
+
+class UserSocialIdentityDetailsEndpoint(UserEndpoint):
+ def delete(self, request, user, identity_id):
+ """
+ Disconnect a Identity from Account
+ ```````````````````````````````````````````````````````
+
+ Disconnects a social auth identity from a sentry account
+
+ :pparam string identity_id: identity id
+ :auth: required
+ """
+
+ try:
+ auth = UserSocialAuth.objects.get(id=identity_id)
+ except UserSocialAuth.DoesNotExist:
+ return Response(status=404)
+
+ backend = get_backend(auth.provider, request, '/')
+ if backend is None:
+ raise Exception('Backend was not found for request: {}'.format(auth.provider))
+
+ # stop this from bubbling up errors to social-auth's middleware
+ # XXX(dcramer): IM SO MAD ABOUT THIS
+ try:
+ backend.disconnect(user, identity_id)
+ except Exception as exc:
+ import sys
+ exc_tb = sys.exc_info()[2]
+ six.reraise(Exception, exc, exc_tb)
+ del exc_tb
+
+ # XXX(dcramer): we experienced an issue where the identity still existed,
+ # and given that this is a cheap query, lets error hard in that case
+ assert not UserSocialAuth.objects.filter(
+ user=user,
+ id=identity_id,
+ ).exists()
+
+ logger.info(
+ 'user.identity.disconnect',
+ extra={
+ 'user_id': user.id,
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'usersocialauth_id': identity_id,
+ }
+ )
+
+ return Response(status=204)
diff --git a/src/sentry/api/endpoints/user_subscriptions.py b/src/sentry/api/endpoints/user_subscriptions.py
new file mode 100644
index 00000000000000..65f9c480679b16
--- /dev/null
+++ b/src/sentry/api/endpoints/user_subscriptions.py
@@ -0,0 +1,79 @@
+from __future__ import absolute_import
+
+from django.utils import timezone
+from rest_framework.response import Response
+from rest_framework import status
+
+from sentry.app import newsletter
+from sentry.api.bases.user import UserEndpoint
+from sentry.models import UserEmail
+
+
+class UserSubscriptionsEndpoint(UserEndpoint):
+ def get(self, request, user):
+ """
+ Retrieve Account Subscriptions
+ `````````````````````````````````````
+
+ Return list of subscriptions for an account
+
+ :auth: required
+ """
+
+ # This returns a dict with `subscriber` and `subscriptions`
+ # Returns `None` if no subscriptions for user
+ sub = newsletter.get_subscriptions(user)
+
+ if sub is None or not newsletter.is_enabled:
+ return Response([])
+
+ try:
+ return Response([{
+ 'listId': x['list_id'],
+ 'listDescription': x['list_description'],
+ 'listName': x['list_name'],
+ 'email': x['email'],
+ 'subscribed': x['subscribed'],
+ 'subscribedDate': x['subscribed_date'],
+ 'unsubscribedDate': x['unsubscribed_date'],
+ } for x in sub['subscriptions']])
+ except KeyError:
+ return Response([])
+
+ def put(self, request, user):
+ """
+ Update Account Subscriptionsoptions
+ `````````````````````````````````
+
+ Update account subscriptions to newsletter
+
+ :param int listId: id of newsletter list
+ :param boolean subscribed: should be subscribed to newsletter
+ :auth: required
+ """
+
+ email = UserEmail.get_primary_email(user)
+
+ # Can't handle subscriptions without a verified email
+ if not email.is_verified:
+ return Response({'details': 'Must have verified email to subscribe to newsletter.'},
+ status=status.HTTP_400_BAD_REQUEST)
+
+ subscribed = request.DATA.get('subscribed')
+ try:
+ list_id = int(request.DATA.get('listId', ''))
+ except ValueError:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+
+ kwargs = {
+ 'list_id': list_id,
+ 'subscribed': subscribed,
+ 'verified': email.is_verified,
+ }
+ if not subscribed:
+ kwargs['unsubscribed_date'] = timezone.now()
+ else:
+ kwargs['subscribed_date'] = timezone.now()
+
+ newsletter.create_or_update_subscription(user, **kwargs)
+ return Response(status=status.HTTP_204_NO_CONTENT)
diff --git a/src/sentry/api/permissions.py b/src/sentry/api/permissions.py
index 768ea4971910d6..3372ed01dfa196 100644
--- a/src/sentry/api/permissions.py
+++ b/src/sentry/api/permissions.py
@@ -2,6 +2,8 @@
from rest_framework import permissions
+from sentry.auth.superuser import is_active_superuser
+
class NoPermission(permissions.BasePermission):
def has_permission(self, request, view):
@@ -42,6 +44,6 @@ def has_object_permission(self, request, view, obj):
class SuperuserPermission(permissions.BasePermission):
def has_permission(self, request, view):
- if request.is_superuser():
+ if is_active_superuser(request):
return True
return False
diff --git a/src/sentry/api/serializers/models/activity.py b/src/sentry/api/serializers/models/activity.py
index 7385ca08a34a37..d6c863dc8e0fb0 100644
--- a/src/sentry/api/serializers/models/activity.py
+++ b/src/sentry/api/serializers/models/activity.py
@@ -4,12 +4,15 @@
import six
from sentry.api.serializers import Serializer, register, serialize
-from sentry.models import Activity, Commit, Group
+from sentry.models import Activity, Commit, Group, PullRequest
from sentry.utils.functional import apply_values
@register(Activity)
class ActivitySerializer(Serializer):
+ def __init__(self, environment_func=None):
+ self.environment_func = environment_func
+
def get_attrs(self, item_list, user):
# TODO(dcramer); assert on relations
users = {d['id']: d for d in serialize(set(i.user for i in item_list if i.user_id), user)}
@@ -27,6 +30,22 @@ def get_attrs(self, item_list, user):
else:
commits = {}
+ pull_request_ids = {
+ i.data['pull_request'] for i in item_list if i.type == Activity.SET_RESOLVED_IN_PULL_REQUEST
+ }
+ if pull_request_ids:
+ pull_request_list = list(PullRequest.objects.filter(id__in=pull_request_ids))
+ pull_requests_by_id = {
+ c.id: d for c, d in zip(
+ pull_request_list, serialize(
+ pull_request_list, user))}
+ pull_requests = {
+ i: pull_requests_by_id.get(i.data['pull_request']) for i in item_list
+ if i.type == Activity.SET_RESOLVED_IN_PULL_REQUEST
+ }
+ else:
+ pull_requests = {}
+
groups = apply_values(
functools.partial(serialize, user=user),
Group.objects.in_bulk(
@@ -48,8 +67,8 @@ def get_attrs(self, item_list, user):
'destination':
groups.get(item.data['destination_id'])
if item.type == Activity.UNMERGE_SOURCE else None,
- 'commit':
- commits.get(item),
+ 'commit': commits.get(item),
+ 'pull_request': pull_requests.get(item),
} for item in item_list
}
@@ -58,6 +77,10 @@ def serialize(self, obj, attrs, user):
data = {
'commit': attrs['commit'],
}
+ elif obj.type == Activity.SET_RESOLVED_IN_PULL_REQUEST:
+ data = {
+ 'pullRequest': attrs['pull_request'],
+ }
elif obj.type == Activity.UNMERGE_DESTINATION:
data = {
'fingerprints': obj.data['fingerprints'],
@@ -82,6 +105,8 @@ def serialize(self, obj, attrs, user):
class OrganizationActivitySerializer(ActivitySerializer):
def get_attrs(self, item_list, user):
+ from sentry.api.serializers import GroupSerializer
+
# TODO(dcramer); assert on relations
attrs = super(OrganizationActivitySerializer, self).get_attrs(
item_list,
@@ -89,7 +114,11 @@ def get_attrs(self, item_list, user):
)
groups = {
- d['id']: d for d in serialize(set(i.group for i in item_list if i.group_id), user)
+ d['id']: d for d in serialize(
+ set([i.group for i in item_list if i.group_id]),
+ user,
+ GroupSerializer(environment_func=self.environment_func)
+ )
}
projects = {d['id']: d for d in serialize(set(i.project for i in item_list), user)}
diff --git a/src/sentry/api/serializers/models/apikey.py b/src/sentry/api/serializers/models/apikey.py
new file mode 100644
index 00000000000000..ae88ddbe9db69c
--- /dev/null
+++ b/src/sentry/api/serializers/models/apikey.py
@@ -0,0 +1,19 @@
+from __future__ import absolute_import
+
+import six
+
+from sentry.api.serializers import Serializer, register
+from sentry.models import ApiKey
+
+
+@register(ApiKey)
+class ApiKeySerializer(Serializer):
+ def serialize(self, obj, attrs, user):
+ return {
+ 'id': six.text_type(obj.id),
+ 'label': obj.label,
+ 'key': obj.key,
+ 'scope_list': obj.scope_list,
+ 'status': obj.status,
+ 'allowed_origins': '' if obj.allowed_origins is None else six.text_type(obj.allowed_origins),
+ }
diff --git a/src/sentry/api/serializers/models/auditlogentry.py b/src/sentry/api/serializers/models/auditlogentry.py
index 9e61325a1c13dc..c5993f704a2e1f 100644
--- a/src/sentry/api/serializers/models/auditlogentry.py
+++ b/src/sentry/api/serializers/models/auditlogentry.py
@@ -6,19 +6,41 @@
from sentry.models import AuditLogEntry
+def fix(data):
+ # There was a point in time where full Team objects
+ # got serialized into our AuditLogEntry.data, so these
+ # values need to be stripped and reduced down to integers
+ if 'teams' not in data:
+ return data
+
+ if not data['teams']:
+ return data
+
+ if not hasattr(data['teams'][0], 'id'):
+ return data
+
+ data['teams'] = [t.id for t in data['teams']]
+ return data
+
+
@register(AuditLogEntry)
class AuditLogEntrySerializer(Serializer):
def get_attrs(self, item_list, user):
# TODO(dcramer); assert on relations
- actors = {
- d['id']: d for d in serialize(set(i.actor for i in item_list if i.actor_id), user)
+ users = {
+ d['id']: d for d in serialize(
+ set(i.actor for i in item_list if i.actor_id) |
+ set(i.target_user for i in item_list if i.target_user_id),
+ user,
+ )
}
return {
item: {
- 'actor': actors[six.text_type(item.actor_id)] if item.actor_id else {
+ 'actor': users[six.text_type(item.actor_id)] if item.actor_id else {
'name': item.get_actor_name(),
},
+ 'targetUser': users.get(six.text_type(item.target_user_id)) or item.target_user_id
} for item in item_list
}
@@ -29,5 +51,8 @@ def serialize(self, obj, attrs, user):
'event': obj.get_event_display(),
'ipAddress': obj.ip_address,
'note': obj.get_note(),
+ 'targetObject': obj.target_object,
+ 'targetUser': attrs['targetUser'],
+ 'data': fix(obj.data),
'dateCreated': obj.datetime,
}
diff --git a/src/sentry/api/serializers/models/auth_provider.py b/src/sentry/api/serializers/models/auth_provider.py
new file mode 100644
index 00000000000000..428e3aeda62366
--- /dev/null
+++ b/src/sentry/api/serializers/models/auth_provider.py
@@ -0,0 +1,28 @@
+from __future__ import absolute_import
+
+import six
+
+from django.core.urlresolvers import reverse
+
+from sentry.api.serializers import Serializer, register
+from sentry.models import AuthProvider, OrganizationMember
+from sentry.utils.http import absolute_uri
+
+
+@register(AuthProvider)
+class AuthProviderSerializer(Serializer):
+ def serialize(self, obj, attrs, user):
+ organization = obj.organization
+ pending_links_count = OrganizationMember.objects.filter(
+ organization=organization,
+ flags=~getattr(OrganizationMember.flags, 'sso:linked'),
+ ).count()
+
+ return {
+ 'id': six.text_type(obj.id),
+ 'provider_name': obj.provider,
+ 'pending_links_count': pending_links_count,
+ 'login_url': absolute_uri(reverse('sentry-organization-home', args=[organization.slug])),
+ 'default_role': organization.default_role,
+ 'require_link': not obj.flags.allow_unlinked,
+ }
diff --git a/src/sentry/api/serializers/models/commit.py b/src/sentry/api/serializers/models/commit.py
index 446e53d80f3aab..14189936ba2c5d 100644
--- a/src/sentry/api/serializers/models/commit.py
+++ b/src/sentry/api/serializers/models/commit.py
@@ -4,11 +4,13 @@
from sentry.api.serializers import Serializer, register, serialize
from sentry.models import Commit, Repository
-from sentry.api.serializers.models.release import get_users_for_authors
+from sentry.api.serializers.models.release import get_users_for_authors, CommitAuthor
def get_users_for_commits(item_list, user=None):
- authors = list(i.author for i in item_list if i.author_id)
+ authors = list(
+ CommitAuthor.objects.filter(id__in=[i.author_id for i in item_list if i.author_id])
+ )
if authors:
org_ids = set(item.organization_id for item in item_list)
@@ -31,9 +33,8 @@ def get_attrs(self, item_list, user):
id__in=[c.repository_id for c in item_list],
)), user
)
- repository_objs = {}
- for repository in repositories:
- repository_objs[repository['id']] = repository
+
+ repository_objs = {repository['id']: repository for repository in repositories}
result = {}
for item in item_list:
diff --git a/src/sentry/api/serializers/models/group.py b/src/sentry/api/serializers/models/group.py
index 6ab696c4f94832..2619033fad575f 100644
--- a/src/sentry/api/serializers/models/group.py
+++ b/src/sentry/api/serializers/models/group.py
@@ -1,20 +1,20 @@
from __future__ import absolute_import, print_function
+import itertools
from collections import defaultdict
from datetime import timedelta
-from itertools import izip
import six
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.utils import timezone
-from sentry import tsdb
+from sentry import tagstore, tsdb
from sentry.api.serializers import Serializer, register, serialize
from sentry.constants import LOG_LEVELS, StatsPeriod
from sentry.models import (
- Group, GroupAssignee, GroupBookmark, GroupMeta, GroupResolution, GroupSeen, GroupSnooze,
- GroupStatus, GroupSubscription, GroupSubscriptionReason, GroupTagKey, User, UserOption,
+ Environment, Group, GroupAssignee, GroupBookmark, GroupMeta, GroupResolution, GroupSeen, GroupSnooze,
+ GroupShare, GroupStatus, GroupSubscription, GroupSubscriptionReason, User, UserOption,
UserOptionValue
)
from sentry.utils.db import attach_foreignkey
@@ -30,64 +30,84 @@
}
+disabled = object()
+
+
@register(Group)
class GroupSerializer(Serializer):
+ def __init__(self, environment_func=None):
+ self.environment_func = environment_func if environment_func is not None else lambda: None
+
def _get_subscriptions(self, item_list, user):
"""
Returns a mapping of group IDs to a two-tuple of (subscribed: bool,
subscription: GroupSubscription or None) for the provided user and
groups.
"""
- results = {group.id: None for group in item_list}
-
- # First, the easy part -- if there is a subscription record associated
- # with the group, we can just use that to know if a user is subscribed
- # or not.
- subscriptions = GroupSubscription.objects.filter(
- group__in=results.keys(),
- user=user,
- )
-
- for subscription in subscriptions:
- results[subscription.group_id] = (subscription.is_active, subscription)
+ if not item_list:
+ return {}
- # For any group that doesn't have a subscription associated with it,
- # we'll need to fall back to the project's option value, so here we
- # collect all of the projects to look up, and keep a set of groups that
+ # Collect all of the projects to look up, and keep a set of groups that
# are part of that project. (Note that the common -- but not only --
# case here is that all groups are part of the same project.)
projects = defaultdict(set)
for group in item_list:
- if results[group.id] is None:
- projects[group.project].add(group.id)
-
- if projects:
- # NOTE: This doesn't use `values_list` because that bypasses field
- # value decoding, so the `value` field would not be unpickled.
- options = {
- option.project_id: option.value
- for option in UserOption.objects.filter(
- Q(project__in=projects.keys()) | Q(project__isnull=True),
- user=user,
- key='workflow:notifications',
- )
- }
+ projects[group.project].add(group)
+
+ # Fetch the options for each project -- we'll need this to identify if
+ # a user has totally disabled workflow notifications for a project.
+ # NOTE: This doesn't use `values_list` because that bypasses field
+ # value decoding, so the `value` field would not be unpickled.
+ options = {
+ option.project_id: option.value
+ for option in
+ UserOption.objects.filter(
+ Q(project__in=projects.keys()) | Q(project__isnull=True),
+ user=user,
+ key='workflow:notifications',
+ )
+ }
+
+ # If there is a subscription record associated with the group, we can
+ # just use that to know if a user is subscribed or not, as long as
+ # notifications aren't disabled for the project.
+ subscriptions = {
+ subscription.group_id: subscription
+ for subscription in
+ GroupSubscription.objects.filter(
+ group__in=list(
+ itertools.chain.from_iterable(
+ itertools.imap(
+ lambda project__groups: project__groups[1] if not options.get(
+ project__groups[0].id,
+ options.get(None)
+ ) == UserOptionValue.no_conversations else [],
+ projects.items(),
+ ),
+ )
+ ),
+ user=user,
+ )
+ }
- # This is the user's default value for any projects that don't have
- # the option value specifically recorded. (The default "all
- # conversations" value is convention.)
- default = options.get(None, UserOptionValue.all_conversations)
-
- # If you're subscribed to all notifications for the project, that
- # means you're subscribed to all of the groups. Otherwise you're
- # not subscribed to any of these leftover groups.
- for project, group_ids in projects.items():
- is_subscribed = options.get(
- project.id,
- default,
- ) == UserOptionValue.all_conversations
- for group_id in group_ids:
- results[group_id] = (is_subscribed, None)
+ # This is the user's default value for any projects that don't have
+ # the option value specifically recorded. (The default "all
+ # conversations" value is convention.)
+ global_default_workflow_option = options.get(None, UserOptionValue.all_conversations)
+
+ results = {}
+ for project, groups in projects.items():
+ project_default_workflow_option = options.get(
+ project.id, global_default_workflow_option)
+ for group in groups:
+ subscription = subscriptions.get(group.id)
+ if subscription is not None:
+ results[group.id] = (subscription.is_active, subscription)
+ else:
+ results[group.id] = (
+ project_default_workflow_option == UserOptionValue.all_conversations,
+ None,
+ ) if project_default_workflow_option != UserOptionValue.no_conversations else disabled
return results
@@ -124,12 +144,41 @@ def get_attrs(self, item_list, user):
).select_related('user')
)
- user_counts = dict(
- GroupTagKey.objects.filter(
- group_id__in=[g.id for g in item_list],
- key='sentry:user',
- ).values_list('group_id', 'values_seen')
- )
+ try:
+ environment = self.environment_func()
+ except Environment.DoesNotExist:
+ user_counts = {}
+ first_seen = {}
+ last_seen = {}
+ times_seen = {}
+ else:
+ project_id = item_list[0].project_id
+ item_ids = [g.id for g in item_list]
+ user_counts = tagstore.get_groups_user_counts(
+ project_id,
+ item_ids,
+ environment_id=environment and environment.id,
+ )
+ first_seen = {}
+ last_seen = {}
+ times_seen = {}
+ if environment is not None:
+ environment_tagvalues = tagstore.get_group_list_tag_value(
+ project_id,
+ item_ids,
+ environment.id,
+ 'environment',
+ environment.name,
+ )
+ for item_id, value in environment_tagvalues.items():
+ first_seen[item_id] = value.first_seen
+ last_seen[item_id] = value.last_seen
+ times_seen[item_id] = value.times_seen
+ else:
+ for item in item_list:
+ first_seen[item.id] = item.first_seen
+ last_seen[item.id] = item.last_seen
+ times_seen[item.id] = item.times_seen
ignore_items = {g.group_id: g for g in GroupSnooze.objects.filter(
group__in=item_list,
@@ -153,10 +202,14 @@ def get_attrs(self, item_list, user):
id__in=actor_ids,
is_active=True,
))
- actors = {u.id: d for u, d in izip(users, serialize(users, user))}
+ actors = {u.id: d for u, d in itertools.izip(users, serialize(users, user))}
else:
actors = {}
+ share_ids = dict(GroupShare.objects.filter(
+ group__in=item_list,
+ ).values_list('group_id', 'uuid'))
+
result = {}
for item in item_list:
active_date = item.active_at or item.first_seen
@@ -192,6 +245,10 @@ def get_attrs(self, item_list, user):
'ignore_actor': ignore_actor,
'resolution': resolution,
'resolution_actor': resolution_actor,
+ 'share_id': share_ids.get(item.id),
+ 'times_seen': times_seen.get(item.id, 0),
+ 'first_seen': first_seen.get(item.id), # TODO: missing?
+ 'last_seen': last_seen.get(item.id),
}
return result
@@ -254,24 +311,40 @@ def serialize(self, obj, attrs, user):
else:
permalink = None
- is_subscribed, subscription = attrs['subscription']
+ subscription_details = None
+ if attrs['subscription'] is not disabled:
+ is_subscribed, subscription = attrs['subscription']
+ if subscription is not None and subscription.is_active:
+ subscription_details = {
+ 'reason': SUBSCRIPTION_REASON_MAP.get(
+ subscription.reason,
+ 'unknown',
+ ),
+ }
+ else:
+ is_subscribed = False
+ subscription_details = {
+ 'disabled': True,
+ }
+
+ share_id = attrs['share_id']
return {
'id': six.text_type(obj.id),
- 'shareId': obj.get_share_id(),
+ 'shareId': share_id,
'shortId': obj.qualified_short_id,
- 'count': six.text_type(obj.times_seen),
+ 'count': six.text_type(attrs['times_seen']),
'userCount': attrs['user_count'],
'title': obj.title,
'culprit': obj.culprit,
'permalink': permalink,
- 'firstSeen': obj.first_seen,
- 'lastSeen': obj.last_seen,
+ 'firstSeen': attrs['first_seen'],
+ 'lastSeen': attrs['last_seen'],
'logger': obj.logger or None,
'level': LOG_LEVELS.get(obj.level, 'unknown'),
'status': status_label,
'statusDetails': status_details,
- 'isPublic': obj.is_public,
+ 'isPublic': share_id is not None,
'project': {
'name': obj.project.name,
'slug': obj.project.slug,
@@ -282,12 +355,7 @@ def serialize(self, obj, attrs, user):
'assignedTo': attrs['assigned_to'],
'isBookmarked': attrs['is_bookmarked'],
'isSubscribed': is_subscribed,
- 'subscriptionDetails': {
- 'reason': SUBSCRIPTION_REASON_MAP.get(
- subscription.reason,
- 'unknown',
- ),
- } if is_subscribed and subscription is not None else None,
+ 'subscriptionDetails': subscription_details,
'hasSeen': attrs['has_seen'],
'annotations': attrs['annotations'],
}
@@ -299,7 +367,9 @@ class StreamGroupSerializer(GroupSerializer):
'24h': StatsPeriod(24, timedelta(hours=1)),
}
- def __init__(self, stats_period=None, matching_event_id=None):
+ def __init__(self, environment_func=None, stats_period=None, matching_event_id=None):
+ super(StreamGroupSerializer, self).__init__(environment_func)
+
if stats_period is not None:
assert stats_period in self.STATS_PERIOD_CHOICES
@@ -315,15 +385,26 @@ def get_attrs(self, item_list, user):
segments, interval = self.STATS_PERIOD_CHOICES[self.stats_period]
now = timezone.now()
- stats = tsdb.get_range(
- model=tsdb.models.group,
- keys=group_ids,
- end=now,
- start=now - ((segments - 1) * interval),
- rollup=int(interval.total_seconds()),
- )
+ query_params = {
+ 'start': now - ((segments - 1) * interval),
+ 'end': now,
+ 'rollup': int(interval.total_seconds()),
+ }
+
+ try:
+ environment = self.environment_func()
+ except Environment.DoesNotExist:
+ stats = {key: tsdb.make_series(0, **query_params) for key in group_ids}
+ else:
+ stats = tsdb.get_range(
+ model=tsdb.models.group,
+ keys=group_ids,
+ environment_id=environment and environment.id,
+ **query_params
+ )
for item in item_list:
+
attrs[item].update({
'stats': stats[item.id],
})
diff --git a/src/sentry/api/serializers/models/grouphash.py b/src/sentry/api/serializers/models/grouphash.py
index b789cf5d3fb542..d982533d0a35da 100644
--- a/src/sentry/api/serializers/models/grouphash.py
+++ b/src/sentry/api/serializers/models/grouphash.py
@@ -19,8 +19,7 @@ def get_latest_events(group_hash_list):
events_by_group_hash = {}
for project_id, group_hash_list_chunk in group_hashes_by_project_id.items():
event_id_list = GroupHash.fetch_last_processed_event_id(
- project_id, [i.id for i in group_hash_list_chunk]
- )
+ [i.id for i in group_hash_list_chunk])
event_by_event_id = {
event.event_id: event
for event in Event.objects.filter(
@@ -30,9 +29,8 @@ def get_latest_events(group_hash_list):
}
for group_hash, event_id in zip(group_hash_list_chunk, event_id_list):
event = event_by_event_id.get(event_id)
- if event is not None and event.group_id != group_hash.group_id:
- event = None
- events_by_group_hash[group_hash] = event
+ if event is not None and event.group_id == group_hash.group_id:
+ events_by_group_hash[group_hash] = event
return [events_by_group_hash.get(group_hash) for group_hash in group_hash_list]
diff --git a/src/sentry/api/serializers/models/grouptagkey.py b/src/sentry/api/serializers/models/grouptagkey.py
deleted file mode 100644
index dd62fb6217a4cc..00000000000000
--- a/src/sentry/api/serializers/models/grouptagkey.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from __future__ import absolute_import
-
-import six
-
-from sentry import tagstore
-from sentry.api.serializers import Serializer, register
-from sentry.models import GroupTagKey
-
-
-@register(GroupTagKey)
-class GroupTagKeySerializer(Serializer):
- def get_attrs(self, item_list, user):
- tag_labels = {
- t.key: t.get_label()
- for t in
- tagstore.get_tag_keys(item_list[0].project_id, [i.key for i in item_list])
- }
-
- result = {}
- for item in item_list:
- key = tagstore.get_standardized_key(item.key)
- try:
- label = tag_labels[item.key]
- except KeyError:
- label = key
- result[item] = {
- 'name': label,
- 'key': key,
- }
-
- return result
-
- def serialize(self, obj, attrs, user):
- return {
- 'id': six.text_type(obj.id),
- 'name': attrs['name'],
- 'key': attrs['key'],
- 'uniqueValues': obj.values_seen,
- }
diff --git a/src/sentry/api/serializers/models/grouptagvalue.py b/src/sentry/api/serializers/models/grouptagvalue.py
deleted file mode 100644
index bec49fdfbd2707..00000000000000
--- a/src/sentry/api/serializers/models/grouptagvalue.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from __future__ import absolute_import
-
-import six
-
-from sentry import tagstore
-from sentry.api.serializers import Serializer, register
-from sentry.models import GroupTagValue, Release
-
-
-@register(GroupTagValue)
-class GroupTagValueSerializer(Serializer):
- def get_attrs(self, item_list, user):
- result = {}
- for item in item_list:
- label = item.value
- if item.key == 'sentry:user':
- if item.value.startswith('id:'):
- label = item.value[len('id:'):]
- elif item.value.startswith('email:'):
- label = item.value[len('email:'):]
- elif item.value.startswith('username:'):
- label = item.value[len('username:'):]
- elif item.value.startswith('ip:'):
- label = item.value[len('ip:'):]
- elif item.key == 'sentry:release':
- label = Release.get_display_version(item.value)
-
- result[item] = {
- 'name': label,
- }
-
- return result
-
- def serialize(self, obj, attrs, user):
- return {
- 'id': six.text_type(obj.id),
- 'name': attrs['name'],
- 'key': tagstore.get_standardized_key(obj.key),
- 'value': obj.value,
- 'count': obj.times_seen,
- 'lastSeen': obj.last_seen,
- 'firstSeen': obj.first_seen,
- }
diff --git a/src/sentry/api/serializers/models/integration.py b/src/sentry/api/serializers/models/integration.py
index ea9c6cd49d5a9c..c3e05062e9f683 100644
--- a/src/sentry/api/serializers/models/integration.py
+++ b/src/sentry/api/serializers/models/integration.py
@@ -14,7 +14,7 @@ def serialize(self, obj, attrs, user):
'id': six.text_type(obj.id),
'name': obj.name,
'provider': {
- 'id': provider.id,
+ 'key': provider.key,
'name': provider.name,
}
}
diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py
index 6af757b2828cb2..ff75428da777c3 100644
--- a/src/sentry/api/serializers/models/organization.py
+++ b/src/sentry/api/serializers/models/organization.py
@@ -8,7 +8,7 @@
from sentry.auth import access
from sentry.models import (
ApiKey, Organization, OrganizationAccessRequest, OrganizationAvatar, OrganizationOnboardingTask,
- OrganizationOption, Team, TeamStatus
+ OrganizationOption, OrganizationStatus, Team, TeamStatus
)
@@ -38,9 +38,15 @@ def serialize(self, obj, attrs, user):
'avatarUuid': None,
}
+ status = OrganizationStatus(obj.status)
+
return {
'id': six.text_type(obj.id),
'slug': obj.slug,
+ 'status': {
+ 'id': status.name.lower(),
+ 'name': status.label,
+ },
'name': obj.name or obj.slug,
'dateCreated': obj.date_added,
'isEarlyAdopter': bool(obj.flags.early_adopter),
@@ -91,29 +97,37 @@ def serialize(self, obj, attrs, user):
feature_list.append('group-unmerge')
if features.has('organizations:integrations-v3', obj, actor=user):
feature_list.append('integrations-v3')
+ if features.has('organizations:new-settings', obj, actor=user):
+ feature_list.append('new-settings')
+ if features.has('organizations:require-2fa', obj, actor=user):
+ feature_list.append('require-2fa')
+ if features.has('organizations:environments', obj, actor=user):
+ feature_list.append('environments')
+ if features.has('organizations:repos', obj, actor=user):
+ feature_list.append('repos')
+ if features.has('organizations:internal-catchall', obj, actor=user):
+ feature_list.append('internal-catchall')
if getattr(obj.flags, 'allow_joinleave'):
feature_list.append('open-membership')
if not getattr(obj.flags, 'disable_shared_issues'):
feature_list.append('shared-issues')
+ if getattr(obj.flags, 'require_2fa'):
+ feature_list.append('require-2fa')
context = super(DetailedOrganizationSerializer, self).serialize(obj, attrs, user)
max_rate = quotas.get_maximum_quota(obj)
context['quota'] = {
- 'maxRate':
- max_rate[0],
- 'maxRateInterval':
- max_rate[1],
- 'accountLimit':
- int(
+ 'maxRate': max_rate[0],
+ 'maxRateInterval': max_rate[1],
+ 'accountLimit': int(
OrganizationOption.objects.get_value(
organization=obj,
key='sentry:account-rate-limit',
default=0,
)
),
- 'projectLimit':
- int(
+ 'projectLimit': int(
OrganizationOption.objects.get_value(
organization=obj,
key='sentry:project-rate-limit',
@@ -121,25 +135,24 @@ def serialize(self, obj, attrs, user):
)
),
}
- context.update(
- {
- 'isDefault': obj.is_default,
- 'defaultRole': obj.default_role,
- 'availableRoles': [{
- 'id': r.id,
- 'name': r.name,
- } for r in roles.get_all()],
- 'openMembership': bool(obj.flags.allow_joinleave),
- 'allowSharedIssues': not obj.flags.disable_shared_issues,
- 'enhancedPrivacy': bool(obj.flags.enhanced_privacy),
- 'dataScrubber': bool(obj.get_option('sentry:require_scrub_data', False)),
- 'dataScrubberDefaults':
- bool(obj.get_option('sentry:require_scrub_defaults', False)),
- 'sensitiveFields': obj.get_option('sentry:sensitive_fields', None) or [],
- 'safeFields': obj.get_option('sentry:safe_fields', None) or [],
- 'scrubIPAddresses': bool(obj.get_option('sentry:require_scrub_ip_address', False)),
- }
- )
+
+ context.update({
+ 'isDefault': obj.is_default,
+ 'defaultRole': obj.default_role,
+ 'availableRoles': [{
+ 'id': r.id,
+ 'name': r.name,
+ } for r in roles.get_all()],
+ 'openMembership': bool(obj.flags.allow_joinleave),
+ 'require2FA': bool(obj.flags.require_2fa),
+ 'allowSharedIssues': not obj.flags.disable_shared_issues,
+ 'enhancedPrivacy': bool(obj.flags.enhanced_privacy),
+ 'dataScrubber': bool(obj.get_option('sentry:require_scrub_data', False)),
+ 'dataScrubberDefaults': bool(obj.get_option('sentry:require_scrub_defaults', False)),
+ 'sensitiveFields': obj.get_option('sentry:sensitive_fields', None) or [],
+ 'safeFields': obj.get_option('sentry:safe_fields', None) or [],
+ 'scrubIPAddresses': bool(obj.get_option('sentry:require_scrub_ip_address', False)),
+ })
context['teams'] = serialize(team_list, user, TeamWithProjectsSerializer())
if env.request:
context['access'] = access.from_request(env.request, obj).scopes
diff --git a/src/sentry/api/serializers/models/organization_access_request.py b/src/sentry/api/serializers/models/organization_access_request.py
new file mode 100644
index 00000000000000..2ac0427f3303a7
--- /dev/null
+++ b/src/sentry/api/serializers/models/organization_access_request.py
@@ -0,0 +1,17 @@
+from __future__ import absolute_import
+
+import six
+
+from sentry.api.serializers import Serializer, register, serialize
+from sentry.models import OrganizationAccessRequest
+
+
+@register(OrganizationAccessRequest)
+class OrganizationAccessRequestSerializer(Serializer):
+ def serialize(self, obj, attrs, user):
+ d = {
+ 'id': six.text_type(obj.id),
+ 'member': serialize(obj.member),
+ 'team': serialize(obj.team),
+ }
+ return d
diff --git a/src/sentry/api/serializers/models/organization_member.py b/src/sentry/api/serializers/models/organization_member.py
index 092bbfba81166b..25f400a1f59aee 100644
--- a/src/sentry/api/serializers/models/organization_member.py
+++ b/src/sentry/api/serializers/models/organization_member.py
@@ -1,16 +1,18 @@
from __future__ import absolute_import
import six
+from collections import defaultdict
from sentry.api.serializers import Serializer, register, serialize
-from sentry.models import OrganizationMember
+from sentry.models import OrganizationMember, OrganizationMemberTeam, Team
@register(OrganizationMember)
class OrganizationMemberSerializer(Serializer):
def get_attrs(self, item_list, user):
# TODO(dcramer): assert on relations
- users = {d['id']: d for d in serialize(set(i.user for i in item_list if i.user_id), user)}
+ users = {d['id']: d for d in serialize(
+ set(i.user for i in item_list if i.user_id), user)}
return {
item: {
@@ -34,3 +36,41 @@ def serialize(self, obj, attrs, user):
'dateCreated': obj.date_added,
}
return d
+
+
+class OrganizationMemberWithTeamsSerializer(OrganizationMemberSerializer):
+ def get_attrs(self, item_list, user):
+ attrs = super(OrganizationMemberWithTeamsSerializer,
+ self).get_attrs(item_list, user)
+
+ member_team_map = list(OrganizationMemberTeam.objects.filter(
+ organizationmember__in=item_list).values(
+ 'organizationmember_id', 'team_id'))
+
+ teams = {team.id: team for team in Team.objects.filter(
+ id__in=[item['team_id'] for item in member_team_map])}
+ results = defaultdict(list)
+
+ # results is a map of member id -> team_slug[]
+ for m in member_team_map:
+ results[m['organizationmember_id']].append(
+ teams[m['team_id']].slug)
+
+ for item in item_list:
+ teams = results.get(item.id, [])
+ try:
+ attrs[item]['teams'] = teams
+ except KeyError:
+ attrs[item] = {
+ 'teams': teams
+ }
+
+ return attrs
+
+ def serialize(self, obj, attrs, user):
+ d = super(OrganizationMemberWithTeamsSerializer,
+ self).serialize(obj, attrs, user)
+
+ d['teams'] = attrs.get('teams', [])
+
+ return d
diff --git a/src/sentry/api/serializers/models/plugin.py b/src/sentry/api/serializers/models/plugin.py
index 6e6151b8f0f5e8..46019bca9e0f43 100644
--- a/src/sentry/api/serializers/models/plugin.py
+++ b/src/sentry/api/serializers/models/plugin.py
@@ -6,6 +6,7 @@
from sentry.utils.assets import get_asset_url
from sentry.utils.http import absolute_uri
from sentry.models import ProjectOption
+from django.utils.text import slugify
class PluginSerializer(Serializer):
@@ -32,10 +33,12 @@ def serialize(self, obj, attrs, user):
d = {
'id': obj.slug,
'name': six.text_type(obj.get_title()),
+ 'slug': obj.slug or slugify(six.text_type(obj.get_title())),
'shortName': six.text_type(obj.get_short_title()),
'type': obj.get_plugin_type(),
'canDisable': obj.can_disable,
'isTestable': hasattr(obj, 'is_testable') and obj.is_testable(),
+ 'hasConfiguration': obj.has_project_conf(),
'metadata': obj.get_metadata(),
'contexts': contexts,
'status': obj.get_status(),
@@ -48,6 +51,24 @@ def serialize(self, obj, attrs, user):
}
if self.project:
d['enabled'] = obj.is_enabled(self.project)
+
+ if obj.version:
+ d['version'] = six.text_type(obj.version)
+
+ if obj.author:
+ d['author'] = {
+ 'name': six.text_type(obj.author),
+ 'url': six.text_type(obj.author_url)
+ }
+
+ if obj.description:
+ d['description'] = six.text_type(obj.description)
+
+ if obj.resource_links:
+ d['resourceLinks'] = [
+ {'title': title, 'url': url} for [title, url] in obj.resource_links
+ ]
+
return d
@@ -59,7 +80,7 @@ def serialize(self, obj, attrs, user):
d = super(PluginWithConfigSerializer, self).serialize(obj, attrs, user)
d['config'] = [
serialize_field(self.project, obj, c)
- for c in obj.get_config(project=self.project, user=user)
+ for c in obj.get_config(project=self.project, user=user, add_additial_fields=True)
]
return d
@@ -80,7 +101,7 @@ def serialize_field(project, plugin, field):
if field.get('type') != 'secret':
data['value'] = plugin.get_option(field['name'], project)
else:
- data['has_saved_value'] = bool(field.get('has_saved_value', False))
+ data['hasSavedValue'] = bool(field.get('has_saved_value', False))
data['prefix'] = field.get('prefix', '')
return data
diff --git a/src/sentry/api/serializers/models/project.py b/src/sentry/api/serializers/models/project.py
index c8adec6c50c7a6..50847303bdfdf2 100644
--- a/src/sentry/api/serializers/models/project.py
+++ b/src/sentry/api/serializers/models/project.py
@@ -8,14 +8,14 @@
from django.db.models.aggregates import Count
from django.utils import timezone
-from sentry import tsdb
+from sentry import tsdb, options
from sentry.api.serializers import register, serialize, Serializer
from sentry.api.serializers.models.plugin import PluginSerializer
from sentry.constants import StatsPeriod
from sentry.digests import backend as digests
from sentry.models import (
- Project, ProjectBookmark, ProjectOption, ProjectPlatform, ProjectStatus, Release, UserOption,
- DEFAULT_SUBJECT_TEMPLATE
+ Project, ProjectBookmark, ProjectOption, ProjectPlatform, ProjectStatus, ProjectTeam,
+ Release, UserOption, DEFAULT_SUBJECT_TEMPLATE
)
from sentry.utils.data_filters import FilterTypes
@@ -104,8 +104,8 @@ def serialize(self, obj, attrs, user):
feature_list = []
for feature in (
- 'global-events', 'data-forwarding', 'rate-limits', 'custom-filters', 'similarity-view',
- 'custom-inbound-filters',
+ 'global-events', 'data-forwarding', 'rate-limits', 'discard-groups', 'similarity-view',
+ 'custom-inbound-filters', 'minidump',
):
if features.has('projects:' + feature, obj, actor=user):
feature_list.append(feature)
@@ -128,6 +128,7 @@ def serialize(self, obj, attrs, user):
'features': feature_list,
'status': status_label,
'platform': obj.platform,
+ 'isInternal': obj.is_internal_project()
}
if 'stats' in attrs:
context['stats'] = attrs['stats']
@@ -158,16 +159,32 @@ def get_attrs(self, item_list, user):
attrs = super(ProjectWithTeamSerializer,
self).get_attrs(item_list, user)
+ project_teams = list(
+ ProjectTeam.objects.filter(
+ project__in=item_list,
+ ).select_related('team')
+ )
+
teams = {d['id']: d for d in serialize(
- list(set(i.team for i in item_list)), user)}
+ list(set(pt.team for pt in project_teams)), user)}
+
+ teams_by_project_id = defaultdict(list)
+ for pt in project_teams:
+ teams_by_project_id[pt.project_id].append(teams[six.text_type(pt.team_id)])
+
for item in item_list:
- attrs[item]['team'] = teams[six.text_type(item.team_id)]
+ attrs[item]['teams'] = teams_by_project_id[item.id]
return attrs
def serialize(self, obj, attrs, user):
data = super(ProjectWithTeamSerializer,
self).serialize(obj, attrs, user)
- data['team'] = attrs['team']
+ # TODO(jess): remove this when this is deprecated
+ try:
+ data['team'] = attrs['teams'][0]
+ except IndexError:
+ pass
+ data['teams'] = attrs['teams']
return data
@@ -187,6 +204,11 @@ class DetailedProjectSerializer(ProjectWithTeamSerializer):
'sentry:blacklisted_ips',
'sentry:releases',
'sentry:error_messages',
+ 'sentry:scrape_javascript',
+ 'sentry:token',
+ 'sentry:token_header',
+ 'sentry:verify_ssl',
+ 'sentry:scrub_ip_address',
'feedback:branding',
'digests:mail:minimum_delay',
'digests:mail:maximum_delay',
@@ -280,19 +302,6 @@ def serialize(self, obj, attrs, user):
'latestRelease':
attrs['latest_release'],
'options': {
- 'sentry:origins':
- '\n'.join(attrs['options'].get(
- 'sentry:origins', ['*']) or []),
- 'sentry:resolve_age':
- int(attrs['options'].get('sentry:resolve_age', 0)),
- 'sentry:scrub_data':
- bool(attrs['options'].get('sentry:scrub_data', True)),
- 'sentry:scrub_defaults':
- bool(attrs['options'].get('sentry:scrub_defaults', True)),
- 'sentry:safe_fields':
- attrs['options'].get('sentry:safe_fields', []),
- 'sentry:sensitive_fields':
- attrs['options'].get('sentry:sensitive_fields', []),
'sentry:csp_ignored_sources_defaults':
bool(attrs['options'].get(
'sentry:csp_ignored_sources_defaults', True)),
@@ -326,10 +335,28 @@ def serialize(self, obj, attrs, user):
digests.maximum_delay,
),
'subjectPrefix':
- attrs['options'].get('mail:subject_prefix'),
+ attrs['options'].get('mail:subject_prefix', options.get('mail.subject-prefix')),
+ 'allowedDomains':
+ attrs['options'].get(
+ 'sentry:origins', ['*']),
+ 'resolveAge':
+ int(attrs['options'].get('sentry:resolve_age', 0)),
+ 'dataScrubber':
+ bool(attrs['options'].get('sentry:scrub_data', True)),
+ 'dataScrubberDefaults':
+ bool(attrs['options'].get('sentry:scrub_defaults', True)),
+ 'safeFields':
+ attrs['options'].get('sentry:safe_fields', []),
+ 'sensitiveFields':
+ attrs['options'].get('sentry:sensitive_fields', []),
'subjectTemplate':
attrs['options'].get(
'mail:subject_template') or DEFAULT_SUBJECT_TEMPLATE.template,
+ 'securityToken': attrs['options'].get('sentry:token') or obj.get_security_token(),
+ 'securityTokenHeader': attrs['options'].get('sentry:token_header'),
+ 'verifySSL': bool(attrs['options'].get('sentry:verify_ssl', False)),
+ 'scrubIPAddresses': bool(attrs['options'].get('sentry:scrub_ip_address', False)),
+ 'scrapeJavaScript': bool(attrs['options'].get('sentry:scrape_javascript', True)),
'organization':
attrs['org'],
'plugins':
@@ -344,7 +371,7 @@ def serialize(self, obj, attrs, user):
'processingIssues':
attrs['processing_issues'],
'defaultEnvironment':
- attrs['options'].get('default_environment'),
+ attrs['options'].get('sentry:default_environment'),
}
)
return data
diff --git a/src/sentry/api/serializers/models/project_key.py b/src/sentry/api/serializers/models/project_key.py
index 4c75f0a2c04110..58464e2e1690cc 100644
--- a/src/sentry/api/serializers/models/project_key.py
+++ b/src/sentry/api/serializers/models/project_key.py
@@ -25,6 +25,7 @@ def serialize(self, obj, attrs, user):
'secret': obj.dsn_private,
'public': obj.dsn_public,
'csp': obj.csp_endpoint,
+ 'minidump': obj.minidump_endpoint,
},
'dateCreated': obj.date_added,
}
diff --git a/src/sentry/api/serializers/models/project_platform.py b/src/sentry/api/serializers/models/project_platform.py
new file mode 100644
index 00000000000000..cf69db5dc61399
--- /dev/null
+++ b/src/sentry/api/serializers/models/project_platform.py
@@ -0,0 +1,15 @@
+from __future__ import absolute_import
+from sentry.api.serializers import register, Serializer
+from sentry.models import ProjectPlatform
+
+
+@register(ProjectPlatform)
+class ProjectPlatformSerializer(Serializer):
+ """
+ Tracks usage of a platform for a given project.
+
+ Note: This model is used solely for analytics.
+ """
+
+ def serialize(self, obj, attrs, user):
+ return {'platform': obj.platform, 'dateCreated': obj.date_added}
diff --git a/src/sentry/api/serializers/models/pullrequest.py b/src/sentry/api/serializers/models/pullrequest.py
new file mode 100644
index 00000000000000..00568ecfa1ca35
--- /dev/null
+++ b/src/sentry/api/serializers/models/pullrequest.py
@@ -0,0 +1,59 @@
+from __future__ import absolute_import
+
+import six
+
+from sentry.api.serializers import Serializer, register, serialize
+from sentry.models import PullRequest, Repository, CommitAuthor
+from sentry.api.serializers.models.release import get_users_for_authors
+
+
+def get_users_for_pull_requests(item_list, user=None):
+ authors = list(
+ CommitAuthor.objects.filter(id__in=[i.author_id for i in item_list if i.author_id])
+ )
+
+ if authors:
+ org_ids = set(item.organization_id for item in item_list)
+ if len(org_ids) == 1:
+ return get_users_for_authors(
+ organization_id=org_ids.pop(),
+ authors=authors,
+ user=user,
+ )
+ return {}
+
+
+@register(PullRequest)
+class PullRequestSerializer(Serializer):
+ def get_attrs(self, item_list, user):
+ users_by_author = get_users_for_pull_requests(item_list, user)
+
+ repositories = serialize(
+ list(Repository.objects.filter(
+ id__in=[c.repository_id for c in item_list],
+ )), user
+ )
+
+ repository_objs = {repository['id']: repository for repository in repositories}
+
+ result = {}
+ for item in item_list:
+ result[item] = {
+ 'repository': repository_objs.get(six.text_type(item.repository_id), {}),
+ 'user': users_by_author.get(six.text_type(item.author_id), {})
+ if item.author_id else {},
+ }
+
+ return result
+
+ def serialize(self, obj, attrs, user):
+ d = {
+ 'id': obj.key,
+ 'title': obj.title,
+ 'message': obj.message,
+ 'dateCreated': obj.date_added,
+ 'repository': attrs['repository'],
+ 'author': attrs['user']
+ }
+
+ return d
diff --git a/src/sentry/api/serializers/models/release.py b/src/sentry/api/serializers/models/release.py
index 9fa8d56d4bdf24..8b7b06ad40db1d 100644
--- a/src/sentry/api/serializers/models/release.py
+++ b/src/sentry/api/serializers/models/release.py
@@ -120,7 +120,7 @@ def _get_commit_metadata(self, item_list, user):
item_authors = []
seen_authors = set()
for user in (users_by_author.get(a) for a in item.authors):
- if user['email'] not in seen_authors:
+ if user and user['email'] not in seen_authors:
seen_authors.add(user['email'])
item_authors.append(user)
@@ -171,8 +171,9 @@ def get_attrs(self, item_list, user, *args, **kwargs):
).distinct())
tags = {}
- tvs = tagstore.get_tag_values(project_ids, 'sentry:release',
- [o.version for o in item_list])
+ tvs = tagstore.get_release_tags(project_ids,
+ environment_id=None,
+ versions=[o.version for o in item_list])
for tv in tvs:
val = tags.get(tv.value)
tags[tv.value] = {
diff --git a/src/sentry/api/serializers/models/role.py b/src/sentry/api/serializers/models/role.py
new file mode 100644
index 00000000000000..cf6c36ae81c61d
--- /dev/null
+++ b/src/sentry/api/serializers/models/role.py
@@ -0,0 +1,19 @@
+from __future__ import absolute_import
+
+import six
+from sentry.api.serializers import Serializer
+
+
+class RoleSerializer(Serializer):
+
+ def serialize(self, obj, attrs, *args, **kwargs):
+ allowed_roles = kwargs.get('allowed_roles') or []
+
+ return {
+ 'id': six.text_type(obj.id),
+ 'name': obj.name,
+ 'desc': obj.desc,
+ 'scopes': obj.scopes,
+ 'is_global': obj.is_global,
+ 'allowed': obj in allowed_roles
+ }
diff --git a/src/sentry/api/serializers/models/rule.py b/src/sentry/api/serializers/models/rule.py
index 1ef829831836e8..b9528842d62142 100644
--- a/src/sentry/api/serializers/models/rule.py
+++ b/src/sentry/api/serializers/models/rule.py
@@ -3,7 +3,7 @@
import six
from sentry.api.serializers import Serializer, register
-from sentry.models import Rule
+from sentry.models import Environment, Rule
def _generate_rule_label(project, rule, data):
@@ -19,7 +19,18 @@ def _generate_rule_label(project, rule, data):
@register(Rule)
class RuleSerializer(Serializer):
+ def get_attrs(self, item_list, user, *args, **kwargs):
+ environments = Environment.objects.in_bulk(
+ filter(None, [i.environment_id for i in item_list]),
+ )
+ return {
+ i: {
+ 'environment': environments.get(i.environment_id)
+ } for i in item_list
+ }
+
def serialize(self, obj, attrs, user):
+ environment = attrs['environment']
d = {
# XXX(dcramer): we currently serialize unsaved rule objects
# as part of the rule editor
@@ -43,5 +54,6 @@ def serialize(self, obj, attrs, user):
obj.label,
'dateCreated':
obj.date_added,
+ 'environment': environment.name if environment is not None else None,
}
return d
diff --git a/src/sentry/api/serializers/models/servicehook.py b/src/sentry/api/serializers/models/servicehook.py
new file mode 100644
index 00000000000000..95f6258182a07f
--- /dev/null
+++ b/src/sentry/api/serializers/models/servicehook.py
@@ -0,0 +1,16 @@
+from __future__ import absolute_import
+
+from sentry.api.serializers import Serializer, register
+from sentry.models import ServiceHook
+
+
+@register(ServiceHook)
+class ServiceHookSerializer(Serializer):
+ def serialize(self, obj, attrs, user):
+ return {
+ 'id': obj.guid,
+ 'url': obj.url,
+ 'secret': obj.secret,
+ 'status': obj.get_status_display(),
+ 'dateCreated': obj.date_added,
+ }
diff --git a/src/sentry/api/serializers/models/tagkey.py b/src/sentry/api/serializers/models/tagkey.py
deleted file mode 100644
index 31b8ddcc2f205e..00000000000000
--- a/src/sentry/api/serializers/models/tagkey.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from __future__ import absolute_import
-
-import six
-
-from sentry import tagstore
-from sentry.api.serializers import Serializer, register
-from sentry.models import TagKey
-
-
-@register(TagKey)
-class TagKeySerializer(Serializer):
- def serialize(self, obj, attrs, user):
- return {
- 'id': six.text_type(obj.id),
- 'key': tagstore.get_standardized_key(obj.key),
- 'name': obj.get_label(),
- 'uniqueValues': obj.values_seen,
- }
diff --git a/src/sentry/api/serializers/models/tagvalue.py b/src/sentry/api/serializers/models/tagvalue.py
index a6f8301a4142b2..668eeb034ba951 100644
--- a/src/sentry/api/serializers/models/tagvalue.py
+++ b/src/sentry/api/serializers/models/tagvalue.py
@@ -2,51 +2,8 @@
import six
-from sentry import tagstore
-from sentry.api.serializers import Serializer, register, serialize
-from sentry.models import EventUser, TagValue
-
-
-@register(TagValue)
-class TagValueSerializer(Serializer):
- def get_attrs(self, item_list, user):
- user_tags = [i.value for i in item_list if i.key == 'sentry:user']
-
- tag_labels = {}
- if user_tags:
- tag_labels.update(
- {
- ('sentry:user', k): v.get_label()
- for k, v in six.iteritems(
- EventUser.for_tags(
- project_id=item_list[0].project_id,
- values=user_tags,
- )
- )
- }
- )
-
- result = {}
- for item in item_list:
- try:
- label = tag_labels[(item.key, item.value)]
- except KeyError:
- label = item.get_label()
- result[item] = {
- 'name': label,
- }
- return result
-
- def serialize(self, obj, attrs, user):
- return {
- 'id': six.text_type(obj.id),
- 'key': tagstore.get_standardized_key(obj.key),
- 'name': attrs['name'],
- 'value': obj.value,
- 'count': obj.times_seen,
- 'lastSeen': obj.last_seen,
- 'firstSeen': obj.first_seen,
- }
+from sentry.api.serializers import Serializer, serialize
+from sentry.models import EventUser
class EnvironmentTagValueSerializer(Serializer):
diff --git a/src/sentry/api/serializers/models/team.py b/src/sentry/api/serializers/models/team.py
index d0b9299b2eac8b..47796e40b79ac0 100644
--- a/src/sentry/api/serializers/models/team.py
+++ b/src/sentry/api/serializers/models/team.py
@@ -5,10 +5,13 @@
from collections import defaultdict
from six.moves import zip
+from sentry import roles
from sentry.app import env
from sentry.api.serializers import Serializer, register, serialize
+from sentry.auth.superuser import is_active_superuser
from sentry.models import (
- OrganizationAccessRequest, OrganizationMemberTeam, Project, ProjectStatus, Team
+ OrganizationAccessRequest, OrganizationMember, OrganizationMemberTeam,
+ ProjectStatus, ProjectTeam, Team
)
@@ -36,16 +39,30 @@ def get_attrs(self, item_list, user):
else:
access_requests = frozenset()
- is_superuser = (request and request.is_superuser() and request.user == user)
+ if user.is_authenticated():
+ # map of org id to role
+ org_roles = {
+ om.organization_id: om.role for om in
+ OrganizationMember.objects.filter(
+ user=user,
+ organization__in=set([t.organization_id for t in item_list]),
+ )}
+ else:
+ org_roles = {}
+
+ is_superuser = (request and is_active_superuser(request) and request.user == user)
result = {}
for team in item_list:
is_member = team.id in memberships
+ org_role = org_roles.get(team.organization_id)
if is_member:
has_access = True
elif is_superuser:
has_access = True
elif team.organization.flags.allow_joinleave:
has_access = True
+ elif org_role and roles.get(org_role).is_global:
+ has_access = True
else:
has_access = False
result[team] = {
@@ -69,24 +86,27 @@ def serialize(self, obj, attrs, user):
class TeamWithProjectsSerializer(TeamSerializer):
def get_attrs(self, item_list, user):
- project_qs = list(
- Project.objects.filter(
+ project_teams = list(
+ ProjectTeam.objects.filter(
team__in=item_list,
- status=ProjectStatus.VISIBLE,
- ).order_by('name', 'slug')
+ project__status=ProjectStatus.VISIBLE,
+ ).order_by('project__name', 'project__slug').select_related('project')
)
- team_map = {i.id: i for i in item_list}
# TODO(dcramer): we should query in bulk for ones we're missing here
orgs = {i.organization_id: i.organization for i in item_list}
- for project in project_qs:
- project._team_cache = team_map[project.team_id]
- project._organization_cache = orgs[project.organization_id]
+ for project_team in project_teams:
+ project_team.project._organization_cache = orgs[project_team.project.organization_id]
+
+ projects = [pt.project for pt in project_teams]
+ projects_by_id = {
+ project.id: data for project, data in zip(projects, serialize(projects, user))
+ }
project_map = defaultdict(list)
- for project, data in zip(project_qs, serialize(project_qs, user)):
- project_map[project.team_id].append(data)
+ for project_team in project_teams:
+ project_map[project_team.team_id].append(projects_by_id[project_team.project_id])
result = super(TeamWithProjectsSerializer, self).get_attrs(item_list, user)
for team in item_list:
diff --git a/src/sentry/api/serializers/models/user.py b/src/sentry/api/serializers/models/user.py
index 3ad44a608b7a98..6e19ef438b5b23 100644
--- a/src/sentry/api/serializers/models/user.py
+++ b/src/sentry/api/serializers/models/user.py
@@ -14,14 +14,16 @@
UserAvatar,
UserOption,
UserEmail,
+ UserPermission,
)
+from sentry.auth.superuser import is_active_superuser
from sentry.utils.avatar import get_gravatar_url
@register(User)
class UserSerializer(Serializer):
def _get_identities(self, item_list, user):
- if not (env.request and env.request.is_superuser()):
+ if not (env.request and is_active_superuser(env.request)):
item_list = [x for x in item_list if x == user]
queryset = AuthIdentity.objects.filter(
@@ -83,12 +85,6 @@ def serialize(self, obj, attrs, user):
)
}
stacktrace_order = int(options.get('stacktrace_order', -1) or -1)
- if stacktrace_order == -1:
- stacktrace_order = 'default'
- elif stacktrace_order == 2:
- stacktrace_order = 'newestFirst'
- elif stacktrace_order == 1:
- stacktrace_order = 'newestLast'
d['options'] = {
'language': options.get('language') or 'en',
@@ -98,6 +94,8 @@ def serialize(self, obj, attrs, user):
'seenReleaseBroadcast': options.get('seen_release_broadcast'),
}
+ d['permissions'] = list(UserPermission.for_user(obj.id))
+
if attrs.get('avatar'):
avatar = {
'avatarType': attrs['avatar'].get_avatar_type_display(),
diff --git a/src/sentry/api/serializers/models/user_social_auth.py b/src/sentry/api/serializers/models/user_social_auth.py
new file mode 100644
index 00000000000000..3dd9213d8b7e6e
--- /dev/null
+++ b/src/sentry/api/serializers/models/user_social_auth.py
@@ -0,0 +1,18 @@
+from __future__ import absolute_import
+
+import six
+
+from django.conf import settings
+from social_auth.models import UserSocialAuth
+
+from sentry.api.serializers import Serializer, register
+
+
+@register(UserSocialAuth)
+class UserSocialAuthSerializer(Serializer):
+ def serialize(self, obj, attrs, user):
+ return {
+ 'id': six.text_type(obj.id),
+ 'provider': obj.provider,
+ 'providerLabel': settings.AUTH_PROVIDER_LABELS[obj.provider],
+ }
diff --git a/src/sentry/api/serializers/models/useremail.py b/src/sentry/api/serializers/models/useremail.py
new file mode 100644
index 00000000000000..b2cd0921cd2c9f
--- /dev/null
+++ b/src/sentry/api/serializers/models/useremail.py
@@ -0,0 +1,15 @@
+from __future__ import absolute_import
+
+from sentry.api.serializers import Serializer, register
+from sentry.models import UserEmail
+
+
+@register(UserEmail)
+class UserEmailSerializer(Serializer):
+ def serialize(self, obj, attrs, user):
+ primary_email = UserEmail.get_primary_email(user)
+ return {
+ 'email': obj.email,
+ 'isPrimary': obj.email == primary_email.email,
+ 'isVerified': obj.is_verified,
+ }
diff --git a/src/sentry/api/serializers/models/userreport.py b/src/sentry/api/serializers/models/userreport.py
index cc40ed2bba7483..4744493730ee03 100644
--- a/src/sentry/api/serializers/models/userreport.py
+++ b/src/sentry/api/serializers/models/userreport.py
@@ -3,7 +3,7 @@
import six
from sentry.api.serializers import register, serialize, Serializer
-from sentry.models import EventUser, UserReport
+from sentry.models import EventUser, UserReport, Event
@register(UserReport)
@@ -12,42 +12,65 @@ def get_attrs(self, item_list, user):
queryset = list(EventUser.objects.filter(
id__in=[i.event_user_id for i in item_list],
))
+
event_users = {e.id: d for e, d in zip(queryset, serialize(queryset, user))}
+ # If a event list with multiple project IDs is passed to this and event IDs are not unique
+ # this could return the wrong eventIDs
+ events_list = Event.objects.filter(
+ project_id__in={i.project_id for i in item_list},
+ event_id__in=[i.event_id for i in item_list]
+ ).values('id', 'event_id')
+
+ events_dict = {e['event_id']: e['id'] for e in events_list}
+
attrs = {}
for item in item_list:
attrs[item] = {
'event_user': event_users.get(item.event_user_id),
+ 'event': {
+ 'id': events_dict.get(item.event_id)
+ }
}
+
return attrs
def serialize(self, obj, attrs, user):
# TODO(dcramer): add in various context from the event
# context == user / http / extra interfaces
return {
- 'id':
- six.text_type(obj.id),
- 'eventID':
- obj.event_id,
+ 'id': six.text_type(obj.id),
+ # TODO(lyn): Verify this isn't being used and eventually remove this from API
+ 'eventID': obj.event_id,
'name': (
obj.name or obj.email or
(attrs['event_user'].get_display_name() if attrs['event_user'] else None)
),
'email': (obj.email or (attrs['event_user'].email if attrs['event_user'] else None)),
- 'comments':
- obj.comments,
- 'dateCreated':
- obj.date_added,
- 'user':
- attrs['event_user'],
+ 'comments': obj.comments,
+ 'dateCreated': obj.date_added,
+ 'user': attrs['event_user'],
+ 'event': {
+ 'id': six.text_type(attrs['event']['id']) if attrs['event']['id'] else None,
+ 'eventID': obj.event_id
+ }
+
}
class ProjectUserReportSerializer(UserReportSerializer):
+ def __init__(self, environment_func=None):
+ self.environment_func = environment_func
+
def get_attrs(self, item_list, user):
+ from sentry.api.serializers import GroupSerializer
+
# TODO(dcramer); assert on relations
groups = {
- d['id']: d for d in serialize(set(i.group for i in item_list if i.group_id), user)
+ d['id']: d for d in serialize(
+ set(i.group for i in item_list if i.group_id),
+ user,
+ GroupSerializer(environment_func=self.environment_func))
}
attrs = super(ProjectUserReportSerializer, self).get_attrs(item_list, user)
diff --git a/src/sentry/api/serializers/rest_framework/origin.py b/src/sentry/api/serializers/rest_framework/origin.py
new file mode 100644
index 00000000000000..39105c4de1e9af
--- /dev/null
+++ b/src/sentry/api/serializers/rest_framework/origin.py
@@ -0,0 +1,29 @@
+from __future__ import absolute_import
+
+from rest_framework import serializers
+
+from sentry.utils.http import parse_uri_match
+
+
+class OriginField(serializers.CharField):
+ # Special case origins that don't fit the normal regex pattern, but are valid
+ WHITELIST_ORIGINS = ('*')
+
+ def from_native(self, data):
+ rv = super(OriginField, self).from_native(data)
+ if not rv:
+ return
+ if not self.is_valid_origin(rv):
+ raise serializers.ValidationError('%s is not an acceptable domain' % rv)
+ return rv
+
+ def is_valid_origin(self, value):
+ if value in self.WHITELIST_ORIGINS:
+ return True
+
+ bits = parse_uri_match(value)
+ # ports are not supported on matching expressions (yet)
+ if ':' in bits.domain:
+ return False
+
+ return True
diff --git a/src/sentry/api/serializers/rest_framework/rule.py b/src/sentry/api/serializers/rest_framework/rule.py
index d3f34a6a07d0cf..b71eab78a38d9e 100644
--- a/src/sentry/api/serializers/rest_framework/rule.py
+++ b/src/sentry/api/serializers/rest_framework/rule.py
@@ -1,7 +1,10 @@
from __future__ import absolute_import
+import six
+
from rest_framework import serializers
+from sentry.models import Environment
from sentry.rules import rules
from . import ListField
@@ -30,14 +33,32 @@ def from_native(self, data):
msg = "Invalid node. Could not find '%s'"
raise ValidationError(msg % data['id'])
- if not cls(self.context['project'], data).validate_form():
- raise ValidationError('Node did not pass validation')
+ node = cls(self.context['project'], data)
+
+ if not node.form_cls:
+ return data
+
+ form = node.get_form_instance()
+
+ if not form.is_valid():
+ # XXX(epurkhiser): Very hacky, but we really just want validation
+ # errors that are more specific, not just 'this wasn't filled out',
+ # give a more generic error for those.
+ first_error = next(six.itervalues(form.errors))[0]
+
+ if first_error != 'This field is required.':
+ raise ValidationError(first_error)
+
+ raise ValidationError(
+ 'Ensure at least one action is enabled and all required fields are filled in.'
+ )
return data
class RuleSerializer(serializers.Serializer):
name = serializers.CharField(max_length=64)
+ environment = serializers.CharField(max_length=64, required=False)
actionMatch = serializers.ChoiceField(
choices=(('all', 'all'), ('any', 'any'), ('none', 'none'), )
)
@@ -49,8 +70,25 @@ class RuleSerializer(serializers.Serializer):
)
frequency = serializers.IntegerField(min_value=5, max_value=60 * 24 * 30)
+ def validate_environment(self, attrs, source):
+ name = attrs.get(source)
+ if name is None:
+ return attrs
+
+ try:
+ attrs['environment'] = Environment.get_for_organization_id(
+ self.context['project'].organization_id,
+ name,
+ ).id
+ except Environment.DoesNotExist:
+ raise serializers.ValidationError(u'This environment has not been created.')
+
+ return attrs
+
def save(self, rule):
rule.project = self.context['project']
+ if self.data.get('environment'):
+ rule.environment_id = self.data['environment']
if self.data.get('name'):
rule.label = self.data['name']
if self.data.get('actionMatch'):
diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py
index 7173b2e7f9da02..8c8688394e2168 100644
--- a/src/sentry/api/urls.py
+++ b/src/sentry/api/urls.py
@@ -7,8 +7,10 @@
from .endpoints.api_authorizations import ApiAuthorizationsEndpoint
from .endpoints.api_tokens import ApiTokensEndpoint
from .endpoints.auth_index import AuthIndexEndpoint
+from .endpoints.authenticator_index import AuthenticatorIndexEndpoint
from .endpoints.broadcast_index import BroadcastIndexEndpoint
from .endpoints.catchall import CatchallEndpoint
+from .endpoints.chunk_upload import ChunkUploadEndpoint
from .endpoints.event_details import EventDetailsEndpoint
from .endpoints.event_apple_crash_report import EventAppleCrashReportEndpoint
from .endpoints.group_details import GroupDetailsEndpoint
@@ -30,11 +32,16 @@
from .endpoints.group_user_reports import GroupUserReportsEndpoint
from .endpoints.index import IndexEndpoint
from .endpoints.internal_queue_tasks import InternalQueueTasksEndpoint
+from .endpoints.internal_quotas import InternalQuotasEndpoint
from .endpoints.internal_stats import InternalStatsEndpoint
-from .endpoints.legacy_project_redirect import LegacyProjectRedirectEndpoint
from .endpoints.organization_access_request_details import OrganizationAccessRequestDetailsEndpoint
from .endpoints.organization_activity import OrganizationActivityEndpoint
from .endpoints.organization_auditlogs import OrganizationAuditLogsEndpoint
+from .endpoints.organization_api_key_index import OrganizationApiKeyIndexEndpoint
+from .endpoints.organization_api_key_details import OrganizationApiKeyDetailsEndpoint
+from .endpoints.organization_auth_providers import OrganizationAuthProvidersEndpoint
+from .endpoints.organization_auth_provider_details import OrganizationAuthProviderDetailsEndpoint
+from .endpoints.organization_auth_provider_send_reminders import OrganizationAuthProviderSendRemindersEndpoint
from .endpoints.organization_details import OrganizationDetailsEndpoint
from .endpoints.organization_shortid import ShortIdLookupEndpoint
from .endpoints.organization_slugs import SlugsUpdateEndpoint
@@ -83,9 +90,14 @@
from .endpoints.project_member_index import ProjectMemberIndexEndpoint
from .endpoints.project_plugins import ProjectPluginsEndpoint
from .endpoints.project_plugin_details import ProjectPluginDetailsEndpoint
+from .endpoints.project_release_details import ProjectReleaseDetailsEndpoint
+from .endpoints.project_release_files import ProjectReleaseFilesEndpoint
+from .endpoints.project_release_file_details import ProjectReleaseFileDetailsEndpoint
+from .endpoints.project_release_commits import ProjectReleaseCommitsEndpoint
from .endpoints.project_releases import ProjectReleasesEndpoint
from .endpoints.project_releases_token import ProjectReleasesTokenEndpoint
from .endpoints.project_rules import ProjectRulesEndpoint
+from .endpoints.project_rules_configuration import ProjectRulesConfigurationEndpoint
from .endpoints.project_rule_details import ProjectRuleDetailsEndpoint
from .endpoints.project_searches import ProjectSearchesEndpoint
from .endpoints.project_search_details import ProjectSearchDetailsEndpoint
@@ -93,25 +105,26 @@
from .endpoints.project_tags import ProjectTagsEndpoint
from .endpoints.project_tagkey_details import ProjectTagKeyDetailsEndpoint
from .endpoints.project_tagkey_values import ProjectTagKeyValuesEndpoint
+from .endpoints.project_team_details import ProjectTeamDetailsEndpoint
+from .endpoints.project_teams import ProjectTeamsEndpoint
from .endpoints.project_processingissues import ProjectProcessingIssuesEndpoint, \
ProjectProcessingIssuesFixEndpoint, ProjectProcessingIssuesDiscardEndpoint
from .endpoints.project_reprocessing import ProjectReprocessingEndpoint
+from .endpoints.project_servicehooks import ProjectServiceHooksEndpoint
+from .endpoints.project_servicehook_details import ProjectServiceHookDetailsEndpoint
from .endpoints.project_user_details import ProjectUserDetailsEndpoint
from .endpoints.project_user_reports import ProjectUserReportsEndpoint
from .endpoints.project_user_stats import ProjectUserStatsEndpoint
from .endpoints.project_users import ProjectUsersEndpoint
from .endpoints.filechange import CommitFileChangeEndpoint
from .endpoints.issues_resolved_in_release import IssuesResolvedInReleaseEndpoint
-from .endpoints.project_release_details import ProjectReleaseDetailsEndpoint
-from .endpoints.project_release_files import ProjectReleaseFilesEndpoint
-from .endpoints.project_release_file_details import ProjectReleaseFileDetailsEndpoint
-from .endpoints.project_release_commits import ProjectReleaseCommitsEndpoint
from .endpoints.release_deploys import ReleaseDeploysEndpoint
from .endpoints.dsym_files import DSymFilesEndpoint, \
UnknownDSymFilesEndpoint, AssociateDSymFilesEndpoint
from .endpoints.shared_group_details import SharedGroupDetailsEndpoint
from .endpoints.system_health import SystemHealthEndpoint
from .endpoints.system_options import SystemOptionsEndpoint
+from .endpoints.sudo import SudoEndpoint
from .endpoints.team_details import TeamDetailsEndpoint
from .endpoints.team_groups_new import TeamGroupsNewEndpoint
from .endpoints.team_groups_trending import TeamGroupsTrendingEndpoint
@@ -119,12 +132,20 @@
from .endpoints.team_project_index import TeamProjectIndexEndpoint
from .endpoints.team_stats import TeamStatsEndpoint
from .endpoints.useravatar import UserAvatarEndpoint
+from .endpoints.user_appearance import UserAppearanceEndpoint
from .endpoints.user_authenticator_details import UserAuthenticatorDetailsEndpoint
from .endpoints.user_identity_details import UserIdentityDetailsEndpoint
from .endpoints.user_index import UserIndexEndpoint
from .endpoints.user_details import UserDetailsEndpoint
+from .endpoints.user_emails import UserEmailsEndpoint
from .endpoints.user_organizations import UserOrganizationsEndpoint
+from .endpoints.user_notification_details import UserNotificationDetailsEndpoint
+from .endpoints.user_social_identities_index import UserSocialIdentitiesIndexEndpoint
+from .endpoints.user_social_identity_details import UserSocialIdentityDetailsEndpoint
+from .endpoints.user_subscriptions import UserSubscriptionsEndpoint
from .endpoints.event_file_committers import EventFileCommittersEndpoint
+from .endpoints.setup_wizard import SetupWizard
+
urlpatterns = patterns(
'',
@@ -145,13 +166,29 @@
ApiAuthorizationsEndpoint.as_view(),
name='sentry-api-0-api-authorizations'
),
- url(r'^api-tokens/$', ApiTokensEndpoint.as_view(), name='sentry-api-0-api-tokens'),
+ url(r'^api-tokens/$', ApiTokensEndpoint.as_view(),
+ name='sentry-api-0-api-tokens'),
# Auth
url(r'^auth/$', AuthIndexEndpoint.as_view(), name='sentry-api-0-auth'),
+ # List Authentiactors
+ url(r'^authenticators/$',
+ AuthenticatorIndexEndpoint.as_view(),
+ name='sentry-api-0-authenticator-index'),
+
+ # Sudo
+ url(r'^sudo/$', SudoEndpoint.as_view(), name='sentry-api-0-sudo'),
+
# Broadcasts
- url(r'^broadcasts/$', BroadcastIndexEndpoint.as_view(), name='sentry-api-0-broadcast-index'),
+ url(r'^broadcasts/$', BroadcastIndexEndpoint.as_view(),
+ name='sentry-api-0-broadcast-index'),
+
+ # Chunk upload
+ url(r'^chunk-upload/$',
+ ChunkUploadEndpoint.as_view(),
+ name='sentry-api-0-chunk-upload'
+ ),
# Users
url(r'^users/$', UserIndexEndpoint.as_view(), name='sentry-api-0-user-index'),
@@ -165,11 +202,21 @@
UserAvatarEndpoint.as_view(),
name='sentry-api-0-user-avatar'
),
+ url(
+ r'^users/(?P[^\/]+)/appearance/$',
+ UserAppearanceEndpoint.as_view(),
+ name='sentry-api-0-user-appearance'
+ ),
url(
r'^users/(?P[^\/]+)/authenticators/(?P[^\/]+)/$',
UserAuthenticatorDetailsEndpoint.as_view(),
name='sentry-api-0-user-authenticator-details'
),
+ url(
+ r'^users/(?P[^\/]+)/emails/$',
+ UserEmailsEndpoint.as_view(),
+ name='sentry-api-0-user-emails'
+ ),
url(
r'^users/(?P[^\/]+)/identities/(?P[^\/]+)/$',
UserIdentityDetailsEndpoint.as_view(),
@@ -180,6 +227,24 @@
UserOrganizationsEndpoint.as_view(),
name='sentry-api-0-user-organizations'
),
+ url(
+ r'^users/(?P[^\/]+)/notifications/$',
+ UserNotificationDetailsEndpoint.as_view(),
+ name='sentry-api-0-user-notifications'
+ ),
+ url(
+ r'^users/(?P[^\/]+)/social-identities/$',
+ UserSocialIdentitiesIndexEndpoint.as_view(),
+ name='sentry-api-0-user-social-identities-index'),
+ url(
+ r'^users/(?P[^\/]+)/social-identities/(?P[^\/]+)/$',
+ UserSocialIdentityDetailsEndpoint.as_view(),
+ name='sentry-api-0-user-social-identity-details'),
+ url(
+ r'^users/(?P[^\/]+)/subscriptions/$',
+ UserSubscriptionsEndpoint.as_view(),
+ name='sentry-api-0-user-subscriptions'
+ ),
# Organizations
url(
@@ -200,6 +265,11 @@
SlugsUpdateEndpoint.as_view(),
name='sentry-api-0-short-ids-update'
),
+ url(
+ r'^organizations/(?P[^\/]+)/access-requests/$',
+ OrganizationAccessRequestDetailsEndpoint.as_view(),
+ name='sentry-api-0-organization-access-requests'
+ ),
url(
r'^organizations/(?P[^\/]+)/access-requests/(?P\d+)/$',
OrganizationAccessRequestDetailsEndpoint.as_view(),
@@ -210,11 +280,36 @@
OrganizationActivityEndpoint.as_view(),
name='sentry-api-0-organization-activity'
),
+ url(
+ r'^organizations/(?P[^\/]+)/api-keys/$',
+ OrganizationApiKeyIndexEndpoint.as_view(),
+ name='sentry-api-0-organization-api-key-index'
+ ),
+ url(
+ r'^organizations/(?P[^\/]+)/api-keys/(?P[^\/]+)/$',
+ OrganizationApiKeyDetailsEndpoint.as_view(),
+ name='sentry-api-0-organization-api-key-details'
+ ),
url(
r'^organizations/(?P[^\/]+)/audit-logs/$',
OrganizationAuditLogsEndpoint.as_view(),
name='sentry-api-0-organization-audit-logs'
),
+ url(
+ r'^organizations/(?P[^\/]+)/auth-provider/$',
+ OrganizationAuthProviderDetailsEndpoint.as_view(),
+ name='sentry-api-0-organization-auth-provider'
+ ),
+ url(
+ r'^organizations/(?P[^\/]+)/auth-providers/$',
+ OrganizationAuthProvidersEndpoint.as_view(),
+ name='sentry-api-0-organization-auth-providers'
+ ),
+ url(
+ r'^organizations/(?P[^\/]+)/auth-provider/send-reminders/$',
+ OrganizationAuthProviderSendRemindersEndpoint.as_view(),
+ name='sentry-api-0-organization-auth-provider-send-reminders'
+ ),
url(
r'^organizations/(?P[^\/]+)/config/integrations/$',
OrganizationConfigIntegrationsEndpoint.as_view(),
@@ -381,15 +476,9 @@
name='sentry-api-0-team-stats'
),
- # Handles redirecting project_id => org_slug/project_slug
- # TODO(dcramer): remove this after a reasonable period of time
- url(
- r'^projects/(?P\d+)/(?P(?:groups|releases|stats|tags)/.*)$',
- LegacyProjectRedirectEndpoint.as_view()
- ),
-
# Projects
- url(r'^projects/$', ProjectIndexEndpoint.as_view(), name='sentry-api-0-projects'),
+ url(r'^projects/$', ProjectIndexEndpoint.as_view(),
+ name='sentry-api-0-projects'),
url(
r'^projects/(?P[^\/]+)/(?P[^\/]+)/$',
ProjectDetailsEndpoint.as_view(),
@@ -435,6 +524,21 @@
EventFileCommittersEndpoint.as_view(),
name='sentry-api-0-event-file-committers'
),
+ url(
+ r'^projects/(?P[^\/]+)/(?P[^\/]+)/files/dsyms/$',
+ DSymFilesEndpoint.as_view(),
+ name='sentry-api-0-dsym-files'
+ ),
+ url(
+ r'^projects/(?P[^\/]+)/(?P[^\/]+)/files/dsyms/unknown/$',
+ UnknownDSymFilesEndpoint.as_view(),
+ name='sentry-api-0-unknown-dsym-files'
+ ),
+ url(
+ r'^projects/(?P[^\/]+)/(?P[^\/]+)/files/dsyms/associate/$',
+ AssociateDSymFilesEndpoint.as_view(),
+ name='sentry-api-0-associate-dsym-files'
+ ),
url(
r'^projects/(?P[^\/]+)/(?P[^\/]+)/filters/$',
ProjectFiltersEndpoint.as_view(),
@@ -445,6 +549,14 @@
ProjectFilterDetailsEndpoint.as_view(),
name='sentry-api-0-project-filters'
),
+ url(
+ r'^projects/(?P[^\/]+)/(?P[^\/]+)/hooks/$',
+ ProjectServiceHooksEndpoint.as_view(),
+ ),
+ url(
+ r'^projects/(?P[^\/]+)/(?P[^\/]+)/hooks/(?P[^\/]+)/$',
+ ProjectServiceHookDetailsEndpoint.as_view(),
+ ),
url(
r'^projects/(?P[^\/]+)/(?P[^\/]+)/(?:issues|groups)/$',
ProjectGroupIndexEndpoint.as_view(),
@@ -509,26 +621,16 @@
ProjectReleaseFileDetailsEndpoint.as_view(),
name='sentry-api-0-project-release-file-details'
),
- url(
- r'^projects/(?P[^\/]+)/(?P[^\/]+)/files/dsyms/$',
- DSymFilesEndpoint.as_view(),
- name='sentry-api-0-dsym-files'
- ),
- url(
- r'^projects/(?P[^\/]+)/(?P[^\/]+)/files/dsyms/unknown/$',
- UnknownDSymFilesEndpoint.as_view(),
- name='sentry-api-0-unknown-dsym-files'
- ),
- url(
- r'^projects/(?P[^\/]+)/(?P[^\/]+)/files/dsyms/associate/$',
- AssociateDSymFilesEndpoint.as_view(),
- name='sentry-api-0-associate-dsym-files'
- ),
url(
r'^projects/(?P[^\/]+)/(?P[^\/]+)/rules/$',
ProjectRulesEndpoint.as_view(),
name='sentry-api-0-project-rules'
),
+ url(
+ r'^projects/(?P[^\/]+)/(?P[^\/]+)/rules/configuration/$',
+ ProjectRulesConfigurationEndpoint.as_view(),
+ name='sentry-api-0-project-rules-configuration'
+ ),
url(
r'^projects/(?P[^\/]+)/(?P[^\/]+)/rules/(?P[^\/]+)/$',
ProjectRuleDetailsEndpoint.as_view(),
@@ -564,6 +666,16 @@
ProjectTagKeyValuesEndpoint.as_view(),
name='sentry-api-0-project-tagkey-values'
),
+ url(
+ r'^projects/(?P[^\/]+)/(?P[^\/]+)/teams/$',
+ ProjectTeamsEndpoint.as_view(),
+ name='sentry-api-0-project-teams'
+ ),
+ url(
+ r'^projects/(?P[^\/]+)/(?P[^\/]+)/teams/(?P[^\/]+)/$',
+ ProjectTeamDetailsEndpoint.as_view(),
+ name='sentry-api-0-project-team-details'
+ ),
url(
r'^projects/(?P[^\/]+)/(?P[^\/]+)/users/$',
ProjectUsersEndpoint.as_view(),
@@ -733,12 +845,30 @@
),
# Internal
- url(r'^internal/health/$', SystemHealthEndpoint.as_view(), name='sentry-api-0-system-health'),
+ url(r'^internal/health/$', SystemHealthEndpoint.as_view(),
+ name='sentry-api-0-system-health'),
url(
r'^internal/options/$', SystemOptionsEndpoint.as_view(), name='sentry-api-0-system-options'
),
+ url(r'^internal/quotas/$', InternalQuotasEndpoint.as_view()),
url(r'^internal/queue/tasks/$', InternalQueueTasksEndpoint.as_view()),
- url(r'^internal/stats/$', InternalStatsEndpoint.as_view(), name='sentry-api-0-internal-stats'),
+ url(r'^internal/stats/$', InternalStatsEndpoint.as_view(),
+ name='sentry-api-0-internal-stats'),
+
+ # Project Wizard
+ url(
+ r'^wizard/$',
+ SetupWizard.as_view(),
+ name='sentry-api-0-project-wizard-new'
+ ),
+
+ url(
+ r'^wizard/(?P[^\/]+)/$',
+ SetupWizard.as_view(),
+ name='sentry-api-0-project-wizard'
+ ),
+
+ # Catch all
url(r'^$', IndexEndpoint.as_view(), name='sentry-api-index'),
url(r'^', CatchallEndpoint.as_view(), name='sentry-api-catchall'),
diff --git a/src/sentry/api/validators/__init__.py b/src/sentry/api/validators/__init__.py
new file mode 100644
index 00000000000000..f5d4d6d12df5f1
--- /dev/null
+++ b/src/sentry/api/validators/__init__.py
@@ -0,0 +1,9 @@
+# XXX(dcramer): we dont use rest framework's serializers module for actual serialization,
+# but rather we use it for validation. Consider this the appropriate place to put these
+# components going forward, though many live in sentry/api/serializers/rest_framework for
+# legacy reasons.
+from __future__ import absolute_import
+
+from sentry.utils.imports import import_submodules
+
+import_submodules(globals(), __name__, __path__)
diff --git a/src/sentry/api/validators/servicehook.py b/src/sentry/api/validators/servicehook.py
new file mode 100644
index 00000000000000..c9b520461fd001
--- /dev/null
+++ b/src/sentry/api/validators/servicehook.py
@@ -0,0 +1,26 @@
+from __future__ import absolute_import
+
+from rest_framework import serializers
+
+from sentry.models import SERVICE_HOOK_EVENTS
+
+from sentry.api.serializers.rest_framework.list import ListField
+
+
+class ServiceHookValidator(serializers.Serializer):
+ url = serializers.URLField(required=True)
+ events = ListField(
+ child=serializers.CharField(max_length=255),
+ required=True,
+ )
+ version = serializers.ChoiceField(choices=(
+ (0, '0'),
+ ), required=False, default=0)
+
+ def validate_events(self, attrs, source):
+ value = attrs[source]
+ if value:
+ for event in value:
+ if event not in SERVICE_HOOK_EVENTS:
+ raise serializers.ValidationError('Invalid event name: {}'.format(event))
+ return attrs
diff --git a/src/sentry/auth/access.py b/src/sentry/auth/access.py
index e253adf59e4d49..a33efeead92903 100644
--- a/src/sentry/auth/access.py
+++ b/src/sentry/auth/access.py
@@ -7,7 +7,8 @@
from django.conf import settings
from sentry import roles
-from sentry.models import AuthIdentity, AuthProvider, OrganizationMember
+from sentry.auth.superuser import is_active_superuser
+from sentry.models import AuthIdentity, AuthProvider, OrganizationMember, UserPermission
def _sso_params(member):
@@ -62,6 +63,12 @@ class BaseAccess(object):
# teams with valid membership
memberships = ()
scopes = frozenset()
+ permissions = frozenset()
+
+ def has_permission(self, permission):
+ if not self.is_active:
+ return False
+ return permission in self.permissions
def has_scope(self, scope):
if not self.is_active:
@@ -93,10 +100,13 @@ class Access(BaseAccess):
# TODO(dcramer): this is still a little gross, and ideally backend access
# would be based on the same scopes as API access so theres clarity in
# what things mean
- def __init__(self, scopes, is_active, teams, memberships, sso_is_valid, requires_sso):
+ def __init__(self, scopes, is_active, teams, memberships,
+ sso_is_valid, requires_sso, permissions=None):
self.teams = teams
self.memberships = memberships
self.scopes = scopes
+ if permissions is not None:
+ self.permissions = permissions
self.is_active = is_active
self.sso_is_valid = sso_is_valid
@@ -107,7 +117,7 @@ def from_request(request, organization, scopes=None):
if not organization:
return DEFAULT
- if request.is_superuser():
+ if is_active_superuser(request):
# we special case superuser so that if they're a member of the org
# they must still follow SSO checks, but they gain global access
try:
@@ -128,6 +138,7 @@ def from_request(request, organization, scopes=None):
memberships=team_list,
sso_is_valid=sso_is_valid,
requires_sso=requires_sso,
+ permissions=UserPermission.for_user(request.user.id),
)
return from_user(request.user, organization, scopes=scopes)
@@ -176,6 +187,7 @@ def from_member(member, scopes=None):
scopes=scopes,
memberships=team_memberships,
teams=team_access,
+ permissions=UserPermission.for_user(member.user_id),
)
@@ -186,6 +198,7 @@ class NoAccess(BaseAccess):
teams = ()
memberships = ()
scopes = frozenset()
+ permissions = frozenset()
DEFAULT = NoAccess()
diff --git a/src/sentry/auth/helper.py b/src/sentry/auth/helper.py
index c93c8ad15cf494..88187d2934330e 100644
--- a/src/sentry/auth/helper.py
+++ b/src/sentry/auth/helper.py
@@ -13,18 +13,21 @@
from django.utils.translation import ugettext_lazy as _
from sentry.app import locks
+from sentry.auth.provider import MigratingIdentityId
from sentry.auth.exceptions import IdentityNotValid
from sentry.models import (
AuditLogEntry, AuditLogEntryEvent, AuthIdentity, AuthProvider, Organization, OrganizationMember,
OrganizationMemberTeam, User, UserEmail
)
from sentry.tasks.auth import email_missing_links
-from sentry.utils import auth
+from sentry.utils import auth, metrics
+from sentry.utils.redis import clusters
from sentry.utils.hashlib import md5_text
from sentry.utils.http import absolute_uri
from sentry.utils.retries import TimedRetryPolicy
from sentry.web.forms.accounts import AuthenticationForm
from sentry.web.helpers import render_to_response
+import sentry.utils.json as json
from . import manager
@@ -41,6 +44,64 @@
ERR_INVALID_IDENTITY = _('The provider did not return a valid user identity.')
+class RedisBackedState(object):
+ # Expire the pipeline after 10 minutes of inactivity.
+ EXPIRATION_TTL = 10 * 60
+
+ def __init__(self, request):
+ self.__dict__['request'] = request
+
+ @property
+ def _client(self):
+ return clusters.get('default').get_local_client_for_key(self.auth_key)
+
+ @property
+ def auth_key(self):
+ return self.request.session.get('auth_key')
+
+ def regenerate(self, initial_state):
+ auth_key = 'auth:pipeline:{}'.format(uuid4().hex)
+
+ self.request.session['auth_key'] = auth_key
+ self.request.session.modified = True
+
+ value = json.dumps(initial_state)
+ self._client.setex(auth_key, self.EXPIRATION_TTL, value)
+
+ def clear(self):
+ if not self.auth_key:
+ return
+
+ self._client.delete(self.auth_key)
+ del self.request.session['auth_key']
+ self.request.session.modified = True
+
+ def is_valid(self):
+ return self.auth_key and self._client.get(self.auth_key)
+
+ def get_state(self):
+ if not self.auth_key:
+ return None
+
+ state_json = self._client.get(self.auth_key)
+ if not state_json:
+ return None
+
+ return json.loads(state_json)
+
+ def __getattr__(self, key):
+ state = self.get_state()
+ return state[key] if state else None
+
+ def __setattr__(self, key, value):
+ state = self.get_state()
+ if not state:
+ return
+
+ state[key] = value
+ self._client.setex(self.auth_key, self.EXPIRATION_TTL, json.dumps(state))
+
+
class AuthHelper(object):
"""
Helper class which is passed into AuthView's.
@@ -68,24 +129,24 @@ class AuthHelper(object):
@classmethod
def get_for_request(cls, request):
- session = request.session.get('auth', {})
- organization_id = session.get('org')
+ state = RedisBackedState(request)
+ if not state.is_valid():
+ return None
+
+ organization_id = state.org_id
if not organization_id:
logging.info('Invalid SSO data found')
return None
- flow = session['flow']
-
- auth_provider_id = session.get('ap')
- provider_key = session.get('p')
+ flow = state.flow
+ auth_provider_id = state.auth_provider
+ provider_key = state.provider_key
if auth_provider_id:
auth_provider = AuthProvider.objects.get(id=auth_provider_id)
elif provider_key:
auth_provider = None
- organization = Organization.objects.get(
- id=session['org'],
- )
+ organization = Organization.objects.get(id=state.org_id)
return cls(
request, organization, flow, auth_provider=auth_provider, provider_key=provider_key
@@ -98,6 +159,7 @@ def __init__(self, request, organization, flow, auth_provider=None, provider_key
self.auth_provider = auth_provider
self.organization = organization
self.flow = flow
+ self.state = RedisBackedState(request)
if auth_provider:
provider = auth_provider.get_provider()
@@ -120,44 +182,40 @@ def __init__(self, request, organization, flow, auth_provider=None, provider_key
self.signature = md5_text(' '.join(av.get_ident() for av in self.pipeline)).hexdigest()
def pipeline_is_valid(self):
- session = self.request.session.get('auth', {})
- if not session:
+ if not self.state.is_valid():
return False
- if session.get('flow') not in (self.FLOW_LOGIN, self.FLOW_SETUP_PROVIDER):
+ if self.state.flow not in (self.FLOW_LOGIN, self.FLOW_SETUP_PROVIDER):
return False
- return session.get('sig') == self.signature
+ return self.state.signature == self.signature
def init_pipeline(self):
- session = {
+ self.state.regenerate({
'uid': self.request.user.id if self.request.user.is_authenticated() else None,
- 'ap': self.auth_provider.id if self.auth_provider else None,
- 'p': self.provider.key,
- 'org': self.organization.id,
- 'idx': 0,
- 'sig': self.signature,
+ 'auth_provider': self.auth_provider.id if self.auth_provider else None,
+ 'provider_key': self.provider.key,
+ 'org_id': self.organization.id,
+ 'step_index': 0,
+ 'signature': self.signature,
'flow': self.flow,
- 'state': {},
- }
- self.request.session['auth'] = session
- self.request.session.modified = True
+ 'data': {},
+ })
def get_redirect_url(self):
return absolute_uri(reverse('sentry-auth-sso'))
def clear_session(self):
- if 'auth' in self.request.session:
- del self.request.session['auth']
- self.request.session.modified = True
+ self.state.clear()
def current_step(self):
"""
Render the current step.
"""
- session = self.request.session['auth']
- idx = session['idx']
- if idx == len(self.pipeline):
+ step_index = self.state.step_index
+
+ if step_index == len(self.pipeline):
return self.finish_pipeline()
- return self.pipeline[idx].dispatch(
+
+ return self.pipeline[step_index].dispatch(
request=self.request,
helper=self,
)
@@ -166,23 +224,21 @@ def next_step(self):
"""
Render the next step.
"""
- self.request.session['auth']['idx'] += 1
- self.request.session.modified = True
+ self.state.step_index += 1
return self.current_step()
def finish_pipeline(self):
- session = self.request.session['auth']
- state = session['state']
+ data = self.fetch_state()
try:
- identity = self.provider.build_identity(state)
+ identity = self.provider.build_identity(data)
except IdentityNotValid:
return self.error(ERR_INVALID_IDENTITY)
- if session['flow'] == self.FLOW_LOGIN:
+ if self.state.flow == self.FLOW_LOGIN:
# create identity and authenticate the user
response = self._finish_login_pipeline(identity)
- elif session['flow'] == self.FLOW_SETUP_PROVIDER:
+ elif self.state.flow == self.FLOW_SETUP_PROVIDER:
response = self._finish_setup_pipeline(identity)
return response
@@ -197,11 +253,6 @@ def _handle_attach_identity(self, identity, member=None):
user = request.user
organization = self.organization
- # On SAML we don't have an id when doing the setup so we
- # don't gonna attach the identity if that was not provided
- if not identity:
- return
-
try:
try:
# prioritize identifying by the SSO provider's user ID
@@ -404,10 +455,7 @@ def _get_identifier(self, identity):
def _find_existing_user(self, email):
return User.objects.filter(
- id__in=UserEmail.objects.filter(
- email__iexact=email,
- is_verified=True,
- ).values('user'),
+ id__in=UserEmail.objects.filter(email__iexact=email).values('user'),
is_active=True,
).first()
@@ -435,11 +483,16 @@ def _handle_unknown_identity(self, identity):
except IndexError:
existing_user = None
+ verified_email = existing_user and existing_user.emails.filter(
+ is_verified=True,
+ email__iexact=identity['email'],
+ ).exists()
+
# If they already have an SSO account and the identity provider says
# the email matches we go ahead and let them merge it. This is the
# only way to prevent them having duplicate accounts, and because
# we trust identity providers, its considered safe.
- if existing_user and existing_user.is_managed:
+ if existing_user and existing_user.is_managed and verified_email:
# we only allow this flow to happen if the existing user has
# membership, otherwise we short circuit because it might be
# an attempt to hijack membership of another organization
@@ -576,6 +629,7 @@ def _handle_existing_identity(self, auth_identity, identity):
return HttpResponseRedirect(auth.get_login_redirect(self.request))
self.clear_session()
+ metrics.incr('sso.login-success', tags={'provider': self.provider.key})
return HttpResponseRedirect(auth.get_login_redirect(self.request))
@@ -595,10 +649,12 @@ def _finish_login_pipeline(self, identity):
their account.
"""
auth_provider = self.auth_provider
+ user_id = identity['id']
+
lock = locks.get(
'sso:auth:{}:{}'.format(
auth_provider.id,
- md5_text(identity['id']).hexdigest(),
+ md5_text(user_id).hexdigest(),
),
duration=5,
)
@@ -606,9 +662,23 @@ def _finish_login_pipeline(self, identity):
try:
auth_identity = AuthIdentity.objects.select_related('user').get(
auth_provider=auth_provider,
- ident=identity['id'],
+ ident=user_id,
)
except AuthIdentity.DoesNotExist:
+ auth_identity = None
+
+ # Handle migration of identity keys
+ if not auth_identity and isinstance(user_id, MigratingIdentityId):
+ try:
+ auth_identity = AuthIdentity.objects.select_related('user').get(
+ auth_provider=auth_provider,
+ ident=user_id.legacy_id,
+ )
+ auth_identity.update(ident=user_id.id)
+ except AuthIdentity.DoesNotExist:
+ auth_identity = None
+
+ if not auth_identity:
return self._handle_unknown_identity(identity)
# If the User attached to this AuthIdentity is not active,
@@ -635,11 +705,11 @@ def _finish_setup_pipeline(self, identity):
if not request.user.is_authenticated():
return self.error(ERR_NOT_AUTHED)
- if request.user.id != request.session['auth']['uid']:
+ if request.user.id != self.state.uid:
return self.error(ERR_UID_MISMATCH)
- state = request.session['auth']['state']
- config = self.provider.build_config(state)
+ data = self.fetch_state()
+ config = self.provider.build_config(data)
try:
om = OrganizationMember.objects.get(
@@ -668,9 +738,7 @@ def _finish_setup_pipeline(self, identity):
data=self.auth_provider.get_audit_log_data(),
)
- email_missing_links.delay(
- organization_id=self.organization.id,
- )
+ email_missing_links.delay(self.organization.id, request.user.id, self.provider.key)
messages.add_message(
self.request,
@@ -697,16 +765,22 @@ def respond(self, template, context=None, status=200):
return render_to_response(template, default_context, self.request, status=status)
def error(self, message):
- session = self.request.session['auth']
- if session['flow'] == self.FLOW_LOGIN:
+ redirect_uri = '/'
+
+ if self.state.flow == self.FLOW_LOGIN:
# create identity and authenticate the user
redirect_uri = reverse('sentry-auth-organization', args=[self.organization.slug])
- elif session['flow'] == self.FLOW_SETUP_PROVIDER:
+ elif self.state.flow == self.FLOW_SETUP_PROVIDER:
redirect_uri = reverse(
'sentry-organization-auth-settings', args=[self.organization.slug]
)
+ metrics.incr('sso.error', tags={
+ 'provider': self.provider.key,
+ 'flow': self.state.flow
+ })
+
messages.add_message(
self.request,
messages.ERROR,
@@ -716,11 +790,10 @@ def error(self, message):
return HttpResponseRedirect(redirect_uri)
def bind_state(self, key, value):
- self.request.session['auth']['state'][key] = value
- self.request.session.modified = True
+ data = self.state.data
+ data[key] = value
- def fetch_state(self, key=None):
- if key is None:
- return self.request.session['auth']['state']
+ self.state.data = data
- return self.request.session['auth']['state'].get(key)
+ def fetch_state(self, key=None):
+ return self.state.data if key is None else self.state.data.get(key)
diff --git a/src/sentry/auth/provider.py b/src/sentry/auth/provider.py
index 98e8a443d363ee..98c676cc3cc005 100644
--- a/src/sentry/auth/provider.py
+++ b/src/sentry/auth/provider.py
@@ -1,16 +1,31 @@
from __future__ import absolute_import, print_function
import logging
+from collections import namedtuple
from .view import ConfigureView
+class MigratingIdentityId(namedtuple('MigratingIdentityId', ['id', 'legacy_id'])):
+ """
+ MigratingIdentityId may be used in the ``id`` field of an identity
+ dictionary to facilitate migrating user identites from one identifying id
+ to another.
+ """
+ __slots__ = ()
+
+ def __unicode__(self):
+ # Default to id when coercing for query lookup
+ return self.id
+
+
class Provider(object):
"""
A provider indicates how authenticate should happen for a given service,
including its configuration and basic identity management.
"""
name = None
+ required_feature = None
def __init__(self, key, **config):
self.key = key
@@ -61,6 +76,10 @@ def build_identity(self, state):
The ``email`` and ``id`` keys are required, ``name`` is optional.
+ The ``id`` may be passed in as a ``MigratingIdentityId`` should the
+ the id key be migrating from one value to another and have multiple
+ lookup values.
+
If the identity can not be constructed an ``IdentityNotValid`` error
should be raised.
"""
diff --git a/src/sentry/auth/providers/dummy.py b/src/sentry/auth/providers/dummy.py
index ddd250fc5c97ce..70e36c7fc7474c 100644
--- a/src/sentry/auth/providers/dummy.py
+++ b/src/sentry/auth/providers/dummy.py
@@ -3,12 +3,14 @@
from django.http import HttpResponse
from sentry.auth import Provider, AuthView
+from sentry.auth.provider import MigratingIdentityId
class AskEmail(AuthView):
def dispatch(self, request, helper):
if 'email' in request.POST:
- helper.bind_state('email', request.POST['email'])
+ helper.bind_state('email', request.POST.get('email'))
+ helper.bind_state('legacy_email', request.POST.get('legacy_email'))
return helper.next_step()
return HttpResponse(DummyProvider.TEMPLATE)
@@ -16,15 +18,16 @@ def dispatch(self, request, helper):
class DummyProvider(Provider):
TEMPLATE = ''
+ name = 'Dummy'
def get_auth_pipeline(self):
return [AskEmail()]
def build_identity(self, state):
return {
- 'name': 'Dummy',
- 'id': state['email'],
+ 'id': MigratingIdentityId(id=state['email'], legacy_id=state.get('legacy_email')),
'email': state['email'],
+ 'name': 'Dummy',
}
def refresh_identity(self, auth_identity):
diff --git a/src/sentry/auth/providers/saml2.py b/src/sentry/auth/providers/saml2.py
index 744a76bf610b5b..9c5a54496e2456 100644
--- a/src/sentry/auth/providers/saml2.py
+++ b/src/sentry/auth/providers/saml2.py
@@ -74,6 +74,49 @@ def dispatch(self, request, helper):
return self.redirect(auth.login())
+# With SAML, the SSO request can be initiated by both the Service Provider
+# (sentry) (the typical case) and the Identity Provider. In the second case,
+# the auth assertion is directly posted to the ACS URL. Because the user will
+# not have initiated their SSO flow we must provide a endpoint similar to
+# auth_provider_login, but with support for initing the auth flow.
+class SAML2AcceptACSView(BaseView):
+ @method_decorator(csrf_exempt)
+ def dispatch(self, request, organization_slug):
+ from sentry.auth.helper import AuthHelper
+ helper = AuthHelper.get_for_request(request)
+
+ # SP initiated authentication, request helper is provided
+ if helper:
+ from sentry.web.frontend.auth_provider_login import AuthProviderLoginView
+ sso_login = AuthProviderLoginView()
+ return sso_login.handle(request)
+
+ # IdP initiated authentication. The organizatio_slug must be valid and
+ # an auth provider must exist for this organization to proceed with
+ # IdP initiated SAML auth.
+ try:
+ organization = Organization.objects.get(slug=organization_slug)
+ except Organization.DoesNotExist:
+ messages.add_message(request, messages.ERROR, ERR_NO_SAML_SSO)
+ return self.redirect(reverse('sentry-login'))
+
+ try:
+ auth_provider = AuthProvider.objects.get(organization=organization)
+ except AuthProvider.DoesNotExist:
+ messages.add_message(request, messages.ERROR, ERR_NO_SAML_SSO)
+ return self.redirect(reverse('sentry-login'))
+
+ helper = AuthHelper(
+ request=request,
+ organization=organization,
+ auth_provider=auth_provider,
+ flow=AuthHelper.FLOW_LOGIN,
+ )
+
+ helper.init_pipeline()
+ return helper.current_step()
+
+
class SAML2ACSView(AuthView):
@method_decorator(csrf_exempt)
def dispatch(self, request, helper):
@@ -197,6 +240,7 @@ class SAML2Provider(Provider):
state during setup. The attribute mapping should map the `Attributes`
constants to the Identity Provider attribute keys.
"""
+ required_feature = 'organizations:sso-saml2'
def get_auth_pipeline(self):
return [SAML2LoginView(), SAML2ACSView()]
@@ -284,7 +328,7 @@ def build_saml_config(provider_config, org):
idp = provider_config['idp']
# TODO(epurkhiser): This is also available in the helper and should probably come from there.
- acs_url = absolute_uri(reverse('sentry-auth-sso'))
+ acs_url = absolute_uri(reverse('sentry-auth-organization-saml-acs', args=[org]))
sls_url = absolute_uri(reverse('sentry-auth-organization-saml-sls', args=[org]))
metadata_url = absolute_uri(reverse('sentry-auth-organization-saml-metadata', args=[org]))
diff --git a/src/sentry/auth/superuser.py b/src/sentry/auth/superuser.py
new file mode 100644
index 00000000000000..2de351aac75bd3
--- /dev/null
+++ b/src/sentry/auth/superuser.py
@@ -0,0 +1,285 @@
+"""
+Superuser in Sentry works differently than the native Django implementation.
+
+In Sentry a user must achieve the following to be treated as a superuser:
+
+- ``User.is_superuser`` must be True
+- If configured, the user must be accessing Sentry from a privileged IP (``SUPERUSER_ALLOWED_IPS``)
+- The user must have a valid 'superuser session', which is a secondary session on top of their
+ standard auth. This session has a shorter lifespan.
+"""
+
+from __future__ import absolute_import
+
+import ipaddress
+import logging
+import six
+
+from datetime import datetime, timedelta
+from django.conf import settings
+from django.core.signing import BadSignature
+from django.utils import timezone
+from django.utils.crypto import constant_time_compare, get_random_string
+
+logger = logging.getLogger('sentry.superuser')
+
+SESSION_KEY = '_su'
+
+COOKIE_NAME = getattr(settings, 'SUPERUSER_COOKIE_NAME', 'su')
+
+COOKIE_SALT = getattr(settings, 'SUPERUSER_COOKIE_SALT', '')
+
+COOKIE_SECURE = getattr(settings, 'SUPERUSER_COOKIE_SECURE', settings.SESSION_COOKIE_SECURE)
+
+COOKIE_DOMAIN = getattr(settings, 'SUPERUSER_COOKIE_DOMAIN', settings.SESSION_COOKIE_DOMAIN)
+
+COOKIE_PATH = getattr(settings, 'SUPERUSER_COOKIE_PATH', settings.SESSION_COOKIE_PATH)
+
+COOKIE_HTTPONLY = getattr(settings, 'SUPERUSER_COOKIE_HTTPONLY', True)
+
+# the maximum time the session can stay alive
+MAX_AGE = getattr(settings, 'SUPERUSER_MAX_AGE', timedelta(hours=4))
+
+# the maximum time the session can stay alive without making another request
+IDLE_MAX_AGE = getattr(settings, 'SUPERUSER_IDLE_MAX_AGE', timedelta(minutes=30))
+
+ALLOWED_IPS = frozenset(getattr(settings, 'SUPERUSER_ALLOWED_IPS', settings.INTERNAL_IPS) or ())
+
+UNSET = object()
+
+
+def is_active_superuser(request):
+ su = getattr(request, 'superuser', None) or Superuser(request)
+ return su.is_active
+
+
+class Superuser(object):
+ allowed_ips = [
+ ipaddress.ip_network(six.text_type(v), strict=False) for v in ALLOWED_IPS
+ ]
+
+ def __init__(self, request, allowed_ips=UNSET, current_datetime=None):
+ self.request = request
+ if allowed_ips is not UNSET:
+ self.allowed_ips = frozenset(
+ ipaddress.ip_network(six.text_type(v), strict=False) for v in allowed_ips or ()
+ )
+ self._populate(current_datetime=current_datetime)
+
+ def is_privileged_request(self):
+ allowed_ips = self.allowed_ips
+ # if there's no IPs configured, we allow assume its the same as *
+ if not allowed_ips:
+ return True
+ ip = ipaddress.ip_address(six.text_type(self.request.META['REMOTE_ADDR']))
+ if not any(ip in addr for addr in allowed_ips):
+ return False
+ return True
+
+ def get_session_data(self, current_datetime=None):
+ """
+ Return the current session data, with native types coerced.
+ """
+ request = self.request
+ data = request.session.get(SESSION_KEY)
+
+ try:
+ cookie_token = request.get_signed_cookie(
+ key=COOKIE_NAME,
+ default=None,
+ salt=COOKIE_SALT,
+ max_age=MAX_AGE.total_seconds()
+ )
+ except BadSignature:
+ logger.exception('superuser.bad-cookie-signature', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': request.user.id,
+ })
+ return
+
+ if not cookie_token:
+ if data:
+ logger.warn('superuser.missing-cookie-token', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': request.user.id,
+ })
+ return False
+ elif not data:
+ logger.warn('superuser.missing-session-data', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': request.user.id,
+ })
+ return
+
+ session_token = data.get('tok')
+ if not session_token:
+ logger.warn('superuser.missing-session-token', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': request.user.id,
+ })
+ return
+
+ if not constant_time_compare(cookie_token, session_token):
+ logger.warn('superuser.invalid-token', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': request.user.id,
+ })
+ return
+
+ if data['uid'] != six.text_type(request.user.id):
+ logger.warn('superuser.invalid-uid', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': request.user.id,
+ 'expected_user_id': data['uid'],
+ })
+ return
+
+ if current_datetime is None:
+ current_datetime = timezone.now()
+
+ try:
+ data['idl'] = datetime.utcfromtimestamp(float(data['idl'])).replace(
+ tzinfo=timezone.utc)
+ except (TypeError, ValueError):
+ logger.warn('superuser.invalid-idle-expiration', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': request.user.id,
+ }, exc_info=True)
+ return
+
+ if data['idl'] < current_datetime:
+ logger.info('superuser.session-expired', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': request.user.id,
+ })
+ return
+
+ try:
+ data['exp'] = datetime.utcfromtimestamp(float(data['exp'])).replace(
+ tzinfo=timezone.utc)
+ except (TypeError, ValueError):
+ logger.warn('superuser.invalid-expiration', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': request.user.id,
+ }, exc_info=True)
+ return
+
+ if data['exp'] < current_datetime:
+ logger.info('superuser.session-expired', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': request.user.id,
+ })
+ return
+
+ return data
+
+ def _populate(self, current_datetime=None):
+ if current_datetime is None:
+ current_datetime = timezone.now()
+
+ request = self.request
+ user = getattr(request, 'user', None)
+ if not hasattr(request, 'session'):
+ data = None
+ elif not (user and user.is_superuser):
+ data = None
+ else:
+ data = self.get_session_data(current_datetime=current_datetime)
+
+ if not data:
+ self._set_logged_out()
+ else:
+ self._set_logged_in(
+ expires=data['exp'],
+ token=data['tok'],
+ user=user,
+ )
+
+ if not self.is_active:
+ logger.warn('superuser.invalid-ip', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': request.user.id,
+ })
+
+ def _set_logged_in(self, expires, token, user, current_datetime=None):
+ # we bind uid here, as if you change users in the same request
+ # we wouldn't want to still support superuser auth (given
+ # the superuser check happens right here)
+ assert user.is_superuser
+ if current_datetime is None:
+ current_datetime = timezone.now()
+ self.token = token
+ self.uid = six.text_type(user.id)
+ # the absolute maximum age of this session
+ self.expires = expires
+ # do we have a valid superuser session?
+ self.is_valid = True
+ # is the session active? (it could be valid, but inactive)
+ self.is_active = self.is_privileged_request()
+ self.request.session[SESSION_KEY] = {
+ 'exp': self.expires.strftime('%s'),
+ 'idl': (current_datetime + IDLE_MAX_AGE).strftime('%s'),
+ 'tok': self.token,
+ # XXX(dcramer): do we really need the uid safety m echanism
+ 'uid': self.uid,
+ }
+
+ def _set_logged_out(self):
+ self.uid = None
+ self.expires = None
+ self.token = None
+ self.is_active = False
+ self.is_valid = False
+ self.request.session.pop(SESSION_KEY, None)
+
+ def set_logged_in(self, user, current_datetime=None):
+ """
+ Mark a session as superuser-enabled.
+ """
+ request = self.request
+ if current_datetime is None:
+ current_datetime = timezone.now()
+ self._set_logged_in(
+ expires=current_datetime + MAX_AGE,
+ token=get_random_string(12),
+ user=user,
+ current_datetime=current_datetime,
+ )
+ logger.info('superuser.logged-in', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': user.id,
+ })
+
+ def set_logged_out(self):
+ """
+ Mark a session as superuser-disabled.
+ """
+ request = self.request
+ self._set_logged_out()
+ logger.info('superuser.logged-out', extra={
+ 'ip_address': request.META['REMOTE_ADDR'],
+ 'user_id': request.user.id,
+ })
+
+ def on_response(self, response, current_datetime=None):
+ request = self.request
+
+ if current_datetime is None:
+ current_datetime = timezone.now()
+
+ # always re-bind the cookie to update the idle expiration window
+ if self.is_active:
+ response.set_signed_cookie(
+ COOKIE_NAME,
+ self.token,
+ salt=COOKIE_SALT,
+ # set max_age to None, as we want this cookie to expire on browser close
+ max_age=None,
+ secure=request.is_secure() if COOKIE_SECURE is None else COOKIE_SECURE,
+ httponly=COOKIE_HTTPONLY,
+ path=COOKIE_PATH,
+ domain=COOKIE_DOMAIN,
+ )
+ # otherwise, if the session is invalid and there's a cookie set, clear it
+ elif not self.is_valid and request.COOKIES.get(COOKIE_NAME):
+ response.delete_cookie(COOKIE_NAME)
diff --git a/src/sentry/auth/utils.py b/src/sentry/auth/utils.py
deleted file mode 100644
index 6073c97947d34e..00000000000000
--- a/src/sentry/auth/utils.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from __future__ import absolute_import
-
-from django.conf import settings
-
-
-def is_internal_ip(request):
- if not settings.INTERNAL_IPS:
- return False
- ip = request.META['REMOTE_ADDR']
- if not any(ip in addr for addr in settings.INTERNAL_IPS):
- return False
- return True
-
-
-def is_privileged_request(request):
- if settings.INTERNAL_IPS:
- return is_internal_ip(request)
- return True
-
-
-def is_active_superuser(request):
- user = getattr(request, 'user', None)
- if not user or not user.is_superuser:
- return False
-
- return is_privileged_request(request)
diff --git a/src/sentry/bgtasks/__init__.py b/src/sentry/bgtasks/__init__.py
new file mode 100644
index 00000000000000..c3961685ab8def
--- /dev/null
+++ b/src/sentry/bgtasks/__init__.py
@@ -0,0 +1 @@
+from __future__ import absolute_import
diff --git a/src/sentry/bgtasks/api.py b/src/sentry/bgtasks/api.py
new file mode 100644
index 00000000000000..cfad1d8fd05c70
--- /dev/null
+++ b/src/sentry/bgtasks/api.py
@@ -0,0 +1,104 @@
+from __future__ import absolute_import
+
+import six
+import time
+import random
+import logging
+import threading
+from contextlib import contextmanager
+
+from django.conf import settings
+
+
+logger = logging.getLogger('sentry.bgtasks')
+tasks = {}
+
+
+def bgtask(roles=None, interval=60):
+ def decorator(f):
+ return BgTask(callback=f, roles=roles, interval=interval)
+ return decorator
+
+
+class BgTask(object):
+
+ def __init__(self, callback, roles=None, interval=60):
+ self.callback = callback
+ self.roles = roles or []
+ self.interval = interval
+ self.running = False
+
+ @property
+ def name(self):
+ return '%s:%s' % (
+ self.callback.__module__,
+ self.callback.__name__,
+ )
+
+ def run(self):
+ if self.running:
+ return
+
+ next_run = time.time() + self.interval * random.random()
+ while self.running:
+ started = time.time()
+ if next_run >= started:
+ try:
+ self.callback()
+ except Exception:
+ logging.error('bgtask.failed', exc_info=True,
+ extra=dict(task_name=self.name))
+ next_run = started + self.interval
+ time.sleep(1.0)
+
+ def reconfigure(self, cfg):
+ if 'roles' in cfg:
+ self.roles = cfg['roles']
+ if 'interval' in cfg:
+ self.interval = cfg['interval']
+
+ def spawn_daemon(self):
+ if self.running:
+ return
+ logger.info('bgtask.spawn', extra=dict(task_name=self.name))
+ t = threading.Thread(target=self.run)
+ t.setDaemon(True)
+ t.start()
+
+ def stop(self):
+ logger.info('bgtask.stop', extra=dict(task_name=self.name))
+ self.running = False
+
+
+def get_task(task_name):
+ module, task_cls = task_name.split(':', 1)
+ mod = __import__(module, None, None, [task_cls])
+ return getattr(mod, task_cls)
+
+
+def spawn_bgtasks(role):
+ for import_name, cfg in six.iteritems(settings.BGTASKS):
+ task = get_task(import_name)
+ # This is already running
+ if task.name in tasks:
+ continue
+ task.reconfigure(cfg)
+ if role not in task.roles:
+ continue
+ task.spawn_daemon()
+ tasks[task.name] = task
+
+
+def shutdown_bgtasks():
+ for task_name, task in list(six.iteritems(tasks)):
+ task.stop()
+ tasks.pop(task_name, None)
+
+
+@contextmanager
+def managed_bgtasks(role):
+ spawn_bgtasks(role)
+ try:
+ yield
+ finally:
+ shutdown_bgtasks()
diff --git a/src/sentry/bgtasks/clean_dsymcache.py b/src/sentry/bgtasks/clean_dsymcache.py
new file mode 100644
index 00000000000000..dd4a345eac646f
--- /dev/null
+++ b/src/sentry/bgtasks/clean_dsymcache.py
@@ -0,0 +1,9 @@
+from __future__ import absolute_import
+
+from sentry.bgtasks.api import bgtask
+from sentry.models import ProjectDSymFile
+
+
+@bgtask()
+def clean_dsymcache():
+ ProjectDSymFile.dsymcache.clear_old_entries()
diff --git a/src/sentry/conf/server.py b/src/sentry/conf/server.py
index 4d7a126dbb51c5..482d46db2dee0f 100644
--- a/src/sentry/conf/server.py
+++ b/src/sentry/conf/server.py
@@ -203,6 +203,7 @@ def env(key, default='', type=None):
)
MIDDLEWARE_CLASSES = (
+ 'sentry.middleware.proxy.ChunkedMiddleware',
'sentry.middleware.proxy.ContentLengthHeaderMiddleware',
'sentry.middleware.security.SecurityHeadersMiddleware',
'sentry.middleware.maintenance.ServicesUnavailableMiddleware',
@@ -253,7 +254,7 @@ def env(key, default='', type=None):
'sentry.analytics.events', 'sentry.nodestore', 'sentry.search', 'sentry.lang.java',
'sentry.lang.javascript', 'sentry.lang.native', 'sentry.plugins.sentry_interface_types',
'sentry.plugins.sentry_mail', 'sentry.plugins.sentry_urls', 'sentry.plugins.sentry_useragents',
- 'sentry.plugins.sentry_webhooks', 'social_auth', 'sudo',
+ 'sentry.plugins.sentry_webhooks', 'social_auth', 'sudo', 'sentry.tagstore',
)
import django
@@ -263,6 +264,10 @@ def env(key, default='', type=None):
STATIC_ROOT = os.path.realpath(os.path.join(PROJECT_ROOT, 'static'))
STATIC_URL = '/_static/{version}/'
+# various middleware will use this to identify resources which should not access
+# cookies
+ANONYMOUS_STATIC_PREFIXES = ('/_static/', '/avatar/')
+
STATICFILES_FINDERS = (
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
@@ -424,10 +429,12 @@ def SOCIAL_AUTH_DEFAULT_USERNAME():
'sentry.tasks.auth', 'sentry.tasks.auto_resolve_issues', 'sentry.tasks.beacon',
'sentry.tasks.check_auth', 'sentry.tasks.clear_expired_snoozes',
'sentry.tasks.collect_project_platforms', 'sentry.tasks.commits', 'sentry.tasks.deletion',
- 'sentry.tasks.digests', 'sentry.tasks.dsymcache', 'sentry.tasks.email', 'sentry.tasks.merge',
+ 'sentry.tasks.digests', 'sentry.tasks.email', 'sentry.tasks.merge',
'sentry.tasks.options', 'sentry.tasks.ping', 'sentry.tasks.post_process',
'sentry.tasks.process_buffer', 'sentry.tasks.reports', 'sentry.tasks.reprocessing',
- 'sentry.tasks.scheduler', 'sentry.tasks.store', 'sentry.tasks.unmerge',
+ 'sentry.tasks.scheduler', 'sentry.tasks.signals', 'sentry.tasks.store', 'sentry.tasks.unmerge',
+ 'sentry.tasks.symcache_update', 'sentry.tasks.servicehooks',
+ 'sentry.tagstore.tasks',
)
CELERY_QUEUES = [
Queue('alerts', routing_key='alerts'),
@@ -544,14 +551,6 @@ def create_partitioned_queues(name):
'expires': 300,
},
},
- # Disabled for the time being:
- # 'clear-old-cached-dsyms': {
- # 'task': 'sentry.tasks.clear_old_cached_dsyms',
- # 'schedule': timedelta(minutes=60),
- # 'options': {
- # 'expires': 3600,
- # },
- # },
'collect-project-platforms': {
'task': 'sentry.tasks.collect_project_platforms',
'schedule': timedelta(days=1),
@@ -588,6 +587,13 @@ def create_partitioned_queues(name):
},
}
+BGTASKS = {
+ 'sentry.bgtasks.clean_dsymcache:clean_dsymcache': {
+ 'interval': 5 * 60,
+ 'roles': ['worker'],
+ }
+}
+
# Sentry logs to two major places: stdout, and it's internal project.
# To disable logging to the internal project, add a logger who's only
# handler is 'console' and disable propagating upwards.
@@ -612,11 +618,25 @@ def create_partitioned_queues(name):
'filters': ['sentry:internal'],
'class': 'raven.contrib.django.handlers.SentryHandler',
},
+ 'metrics': {
+ 'level': 'WARNING',
+ 'filters': ['important_django_request'],
+ 'class': 'sentry.logging.handlers.MetricsLogHandler',
+ },
+ 'django_internal': {
+ 'level': 'WARNING',
+ 'filters': ['sentry:internal', 'important_django_request'],
+ 'class': 'raven.contrib.django.handlers.SentryHandler',
+ },
},
'filters': {
'sentry:internal': {
'()': 'sentry.utils.raven.SentryInternalFilter',
},
+ 'important_django_request': {
+ '()': 'sentry.logging.handlers.MessageContainsFilter',
+ 'contains': ["CSRF"]
+ }
},
'root': {
'level': 'NOTSET',
@@ -627,7 +647,7 @@ def create_partitioned_queues(name):
'overridable': ['celery', 'sentry'],
'loggers': {
'celery': {
- 'level': 'WARN',
+ 'level': 'WARNING',
},
'sentry': {
'level': 'INFO',
@@ -661,8 +681,8 @@ def create_partitioned_queues(name):
'level': 'INFO',
},
'django.request': {
- 'level': 'ERROR',
- 'handlers': ['console'],
+ 'level': 'WARNING',
+ 'handlers': ['console', 'metrics', 'django_internal'],
'propagate': False,
},
'toronado': {
@@ -720,18 +740,26 @@ def create_partitioned_queues(name):
'auth:register': True,
'organizations:api-keys': False,
'organizations:create': True,
+ 'organizations:repos': True,
'organizations:sso': True,
- 'organizations:saml2': False,
+ 'organizations:sso-saml2': False,
+ 'organizations:sso-rippling': False,
'organizations:group-unmerge': False,
'organizations:integrations-v3': False,
+ 'organizations:invite-members': True,
+ 'organizations:new-settings': False,
+ 'organizations:require-2fa': False,
+ 'organizations:environments': False,
+ 'organizations:internal-catchall': False,
'projects:global-events': False,
'projects:plugins': True,
'projects:dsym': False,
'projects:sample-events': True,
'projects:data-forwarding': True,
'projects:rate-limits': True,
- 'projects:custom-filters': False,
+ 'projects:discard-groups': False,
'projects:custom-inbound-filters': False,
+ 'projects:minidump': False,
}
# Default time zone for localization in the UI.
@@ -758,6 +786,7 @@ def create_partitioned_queues(name):
# Default project ID (for internal errors)
SENTRY_PROJECT = 1
+SENTRY_PROJECT_KEY = None
# Project ID for recording frontend (javascript) exceptions
SENTRY_FRONTEND_PROJECT = None
@@ -789,7 +818,9 @@ def create_partitioned_queues(name):
SENTRY_SMTP_PORT = 1025
SENTRY_INTERFACES = {
- 'csp': 'sentry.interfaces.csp.Csp',
+ 'csp': 'sentry.interfaces.security.Csp',
+ 'expectct': 'sentry.interfaces.security.ExpectCT',
+ 'expectstaple': 'sentry.interfaces.security.ExpectStaple',
'device': 'sentry.interfaces.device.Device',
'exception': 'sentry.interfaces.exception.Exception',
'logentry': 'sentry.interfaces.message.Message',
@@ -812,7 +843,7 @@ def create_partitioned_queues(name):
'sentry.interfaces.Query': 'sentry.interfaces.query.Query',
'sentry.interfaces.Http': 'sentry.interfaces.http.Http',
'sentry.interfaces.User': 'sentry.interfaces.user.User',
- 'sentry.interfaces.Csp': 'sentry.interfaces.csp.Csp',
+ 'sentry.interfaces.Csp': 'sentry.interfaces.security.Csp',
'sentry.interfaces.AppleCrashReport': 'sentry.interfaces.applecrash.AppleCrashReport',
'sentry.interfaces.Breadcrumbs': 'sentry.interfaces.breadcrumbs.Breadcrumbs',
'sentry.interfaces.Contexts': 'sentry.interfaces.contexts.Contexts',
@@ -833,6 +864,7 @@ def create_partitioned_queues(name):
SENTRY_ANALYTICS_ALIASES = {
'noop': 'sentry.analytics.Analytics',
+ 'pubsub': 'sentry.analytics.pubsub.PubSubAnalytics',
}
# set of backends that do not support needing SMTP mail.* settings
@@ -852,7 +884,7 @@ def create_partitioned_queues(name):
# make projects public
SENTRY_ALLOW_PUBLIC_PROJECTS = True
-# Can users be invited to organizations?
+# Will an invite be sent when a member is added to an organization?
SENTRY_ENABLE_INVITES = True
# Default to not sending the Access-Control-Allow-Origin header on api/store
@@ -904,8 +936,18 @@ def create_partitioned_queues(name):
SENTRY_NODESTORE_OPTIONS = {}
# Tag storage backend
-SENTRY_TAGSTORE = 'sentry.tagstore.legacy.LegacyTagStorage'
-SENTRY_TAGSTORE_OPTIONS = {}
+_SENTRY_TAGSTORE_DEFAULT_MULTI_OPTIONS = {
+ 'backends': [
+ ('sentry.tagstore.legacy.LegacyTagStorage', {}),
+ ('sentry.tagstore.v2.V2TagStorage', {}),
+ ],
+ 'runner': 'ImmediateRunner',
+}
+SENTRY_TAGSTORE = os.environ.get('SENTRY_TAGSTORE', 'sentry.tagstore.legacy.LegacyTagStorage')
+SENTRY_TAGSTORE_OPTIONS = (
+ _SENTRY_TAGSTORE_DEFAULT_MULTI_OPTIONS if 'SENTRY_TAGSTORE_DEFAULT_MULTI_OPTIONS' in os.environ
+ else {}
+)
# Search backend
SENTRY_SEARCH = 'sentry.search.django.DjangoSearchBackend'
@@ -1215,14 +1257,14 @@ def get_raven_config():
# TODO(dcramer): move this to sentry.io so it can be automated
SDK_VERSIONS = {
- 'raven-js': '3.16.0',
- 'raven-node': '2.1.0',
- 'raven-python': '6.1.0',
- 'raven-ruby': '2.5.3',
- 'sentry-cocoa': '3.1.2',
- 'sentry-java': '1.2.0',
- 'sentry-laravel': '0.7.0',
- 'sentry-php': '1.7.0',
+ 'raven-js': '3.21.0',
+ 'raven-node': '2.3.0',
+ 'raven-python': '6.4.0',
+ 'raven-ruby': '2.7.1',
+ 'sentry-cocoa': '3.11.1',
+ 'sentry-java': '1.6.4',
+ 'sentry-laravel': '0.8.0',
+ 'sentry-php': '1.8.2',
}
SDK_URLS = {
diff --git a/src/sentry/constants.py b/src/sentry/constants.py
index f845feb0c4ba09..6ec42abffb2944 100644
--- a/src/sentry/constants.py
+++ b/src/sentry/constants.py
@@ -37,9 +37,13 @@ def get_all_languages():
MODULE_ROOT = os.path.dirname(__import__('sentry').__file__)
DATA_ROOT = os.path.join(MODULE_ROOT, 'data')
+VERSION_LENGTH = 250
+
SORT_OPTIONS = OrderedDict(
(
- ('priority', _('Priority')), ('date', _('Last Seen')), ('new', _('First Seen')),
+ ('priority', _('Priority')),
+ ('date', _('Last Seen')),
+ ('new', _('First Seen')),
('freq', _('Frequency')),
)
)
@@ -81,7 +85,8 @@ def get_all_languages():
'remote', 'get-cli', 'blog', 'welcome', 'features', 'customers', 'integrations', 'signup',
'pricing', 'subscribe', 'enterprise', 'about', 'jobs', 'thanks', 'guide', 'privacy',
'security', 'terms', 'from', 'sponsorship', 'for', 'at', 'platforms', 'branding', 'vs',
- 'answers', '_admin', 'support', 'contact', 'onboarding', 'ext', 'extension', 'extensions', 'plugins',
+ 'answers', '_admin', 'support', 'contact', 'onboarding', 'ext', 'extension', 'extensions',
+ 'plugins', 'themonitor', 'settings',
)
)
@@ -101,15 +106,13 @@ def get_all_languages():
DEFAULT_ALERT_PROJECT_THRESHOLD = (500, 25) # 500%, 25 events
DEFAULT_ALERT_GROUP_THRESHOLD = (1000, 25) # 1000%, 25 events
-# Default paginator value
-EVENTS_PER_PAGE = 15
-
# Default sort option for the group stream
DEFAULT_SORT_OPTION = 'date'
# Setup languages for only available locales
LANGUAGE_MAP = dict(settings.LANGUAGES)
-LANGUAGES = [(k, LANGUAGE_MAP[k]) for k in get_all_languages() if k in LANGUAGE_MAP]
+LANGUAGES = [(k, LANGUAGE_MAP[k])
+ for k in get_all_languages() if k in LANGUAGE_MAP]
# TODO(dcramer): We eventually want to make this user-editable
TAG_LABELS = {
@@ -122,6 +125,12 @@ def get_all_languages():
'server_name': 'Server',
}
+PROTECTED_TAG_KEYS = frozenset([
+ 'environment',
+ 'release',
+ 'sentry:release',
+])
+
# TODO(dcramer): once this is more flushed out we want this to be extendable
SENTRY_RULES = (
'sentry.rules.actions.notify_event.NotifyEventAction',
@@ -137,12 +146,13 @@ def get_all_languages():
)
# methods as defined by http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html + PATCH
-HTTP_METHODS = ('GET', 'POST', 'PUT', 'OPTIONS', 'HEAD', 'DELETE', 'TRACE', 'CONNECT', 'PATCH')
+HTTP_METHODS = ('GET', 'POST', 'PUT', 'OPTIONS', 'HEAD',
+ 'DELETE', 'TRACE', 'CONNECT', 'PATCH')
CLIENT_RESERVED_ATTRS = (
'project', 'errors', 'event_id', 'message', 'checksum', 'culprit', 'fingerprint', 'level',
'time_spent', 'logger', 'server_name', 'site', 'received', 'timestamp', 'extra', 'modules',
- 'tags', 'platform', 'release', 'dist', 'environment',
+ 'tags', 'platform', 'release', 'dist', 'environment', 'transaction', 'key_id',
)
# XXX: Must be all lowercase
@@ -180,6 +190,7 @@ def get_all_languages():
'elixir',
'haskell',
'groovy',
+ 'native',
]
)
@@ -201,7 +212,9 @@ def get_all_languages():
# Known dsym mimetypes
KNOWN_DSYM_TYPES = {
+ 'text/x-breakpad': 'breakpad',
'application/x-mach-binary': 'macho',
+ 'application/x-elf-binary': 'elf',
'text/x-proguard+plain': 'proguard',
}
@@ -242,6 +255,7 @@ def _load_platform_data():
MARKETING_SLUG_TO_INTEGRATION_ID = {
"kotlin": "java",
"scala": "java",
+ "spring": "java",
"android": "java-android",
"react": "javascript-react",
"angular": "javascript-angular",
@@ -263,6 +277,7 @@ def _load_platform_data():
"symfony": "php-symfony2",
"rails": "ruby-rails",
"sinatra": "ruby-sinatra",
+ "dotnet": "csharp",
}
@@ -307,7 +322,8 @@ def get_integration_id_for_event(platform, sdk_name, integrations):
return integration_id
# try sdk name, for example "sentry-java" -> "java" or "raven-java:log4j" -> "java-log4j"
- sdk_name = sdk_name.lower().replace("sentry-", "").replace("raven-", "").replace(":", "-")
+ sdk_name = sdk_name.lower().replace(
+ "sentry-", "").replace("raven-", "").replace(":", "-")
if sdk_name in INTEGRATION_ID_TO_PLATFORM_DATA:
return sdk_name
@@ -322,11 +338,15 @@ class ObjectStatus(object):
PENDING_DELETION = 2
DELETION_IN_PROGRESS = 3
+ ACTIVE = 0
+ DISABLED = 1
+
@classmethod
def as_choices(cls):
return (
- (cls.VISIBLE, 'visible'), (cls.HIDDEN,
- 'hidden'), (cls.PENDING_DELETION, 'pending_deletion'),
+ (cls.ACTIVE, 'active'),
+ (cls.DISABLED, 'disabled'),
+ (cls.PENDING_DELETION, 'pending_deletion'),
(cls.DELETION_IN_PROGRESS, 'deletion_in_progress'),
)
diff --git a/src/sentry/coreapi.py b/src/sentry/coreapi.py
index d5fc77814519be..18f006dc832c1f 100644
--- a/src/sentry/coreapi.py
+++ b/src/sentry/coreapi.py
@@ -11,45 +11,32 @@
from __future__ import absolute_import, print_function
import base64
+import jsonschema
import logging
+import re
import six
-import uuid
import zlib
-import re
from collections import MutableMapping
-from datetime import datetime, timedelta
from django.core.exceptions import SuspiciousOperation
from django.utils.crypto import constant_time_compare
from gzip import GzipFile
from six import BytesIO
from time import time
-from sentry import filters, tagstore
+from sentry import filters
from sentry.cache import default_cache
-from sentry.constants import (
- CLIENT_RESERVED_ATTRS,
- DEFAULT_LOG_LEVEL,
- LOG_LEVELS_MAP,
- MAX_TAG_VALUE_LENGTH,
- MAX_TAG_KEY_LENGTH,
- VALID_PLATFORMS,
-)
-from sentry.db.models import BoundedIntegerField
-from sentry.interfaces.base import get_interface, InterfaceValidationError
-from sentry.interfaces.csp import Csp
+from sentry.interfaces.base import get_interface
from sentry.event_manager import EventManager
-from sentry.models import EventError, ProjectKey
+from sentry.models import ProjectKey
from sentry.tasks.store import preprocess_event, \
preprocess_event_from_reprocessing
from sentry.utils import json
from sentry.utils.auth import parse_auth_header
-from sentry.utils.csp import is_valid_csp_report
from sentry.utils.http import origin_from_request
from sentry.utils.data_filters import is_valid_ip, \
is_valid_release, is_valid_error_message, FilterStatKeys
from sentry.utils.strings import decompress
-from sentry.utils.validators import is_float, is_event_id
try:
# Attempt to load ujson if it's installed.
@@ -97,14 +84,6 @@ def __init__(self, retry_after=None):
self.retry_after = retry_after
-class InvalidTimestamp(Exception):
- pass
-
-
-class InvalidFingerprint(Exception):
- pass
-
-
class Auth(object):
def __init__(self, auth_vars, is_public=False):
self.client = auth_vars.get('sentry_client')
@@ -316,71 +295,16 @@ def safely_load_json_string(self, json_string):
(type(e).__name__, e))
return obj
- def _process_data_timestamp(self, data, current_datetime=None):
- value = data['timestamp']
- if not value:
- del data['timestamp']
- return data
- elif is_float(value):
- try:
- value = datetime.fromtimestamp(float(value))
- except Exception:
- raise InvalidTimestamp(
- 'Invalid value for timestamp: %r' % data['timestamp'])
- elif not isinstance(value, datetime):
- # all timestamps are in UTC, but the marker is optional
- if value.endswith('Z'):
- value = value[:-1]
- if '.' in value:
- # Python doesn't support long microsecond values
- # https://github.com/getsentry/sentry/issues/1610
- ts_bits = value.split('.', 1)
- value = '%s.%s' % (ts_bits[0], ts_bits[1][:2])
- fmt = '%Y-%m-%dT%H:%M:%S.%f'
- else:
- fmt = '%Y-%m-%dT%H:%M:%S'
- try:
- value = datetime.strptime(value, fmt)
- except Exception:
- raise InvalidTimestamp(
- 'Invalid value for timestamp: %r' % data['timestamp'])
-
- if current_datetime is None:
- current_datetime = datetime.now()
-
- if value > current_datetime + timedelta(minutes=1):
- raise InvalidTimestamp(
- 'Invalid value for timestamp (in future): %r' % value)
-
- if value < current_datetime - timedelta(days=30):
- raise InvalidTimestamp(
- 'Invalid value for timestamp (too old): %r' % value)
-
- data['timestamp'] = float(value.strftime('%s'))
-
- return data
-
- def _process_fingerprint(self, data):
- if not isinstance(data['fingerprint'], (list, tuple)):
- raise InvalidFingerprint
-
- result = []
- for bit in data['fingerprint']:
- if not isinstance(bit, six.string_types + six.integer_types + (float, )):
- raise InvalidFingerprint
- result.append(six.text_type(bit))
- return result
-
def parse_client_as_sdk(self, value):
if not value:
- return
+ return {}
try:
name, version = value.split('/', 1)
except ValueError:
try:
name, version = value.split(' ', 1)
except ValueError:
- return
+ return {}
return {
'name': name,
'version': version,
@@ -406,6 +330,11 @@ def should_filter(self, project, data, ip_address=None):
if error_message and not is_valid_error_message(project, error_message):
return (True, FilterStatKeys.ERROR_MESSAGE)
+ for exception_interface in data.get('sentry.interfaces.Exception', {}).get('values', []):
+ message = u': '.join(filter(None, map(exception_interface.get, ['type', 'value'])))
+ if message and not is_valid_error_message(project, message):
+ return (True, FilterStatKeys.ERROR_MESSAGE)
+
for filter_cls in filters.all():
filter_obj = filter_cls(project)
if filter_obj.is_enabled() and filter_obj.test(data):
@@ -413,402 +342,7 @@ def should_filter(self, project, data, ip_address=None):
return (False, None)
- def validate_data(self, project, data):
- # TODO(dcramer): move project out of the data packet
- data['project'] = project.id
-
- data['errors'] = []
-
- if data.get('culprit'):
- if not isinstance(data['culprit'], six.string_types):
- raise APIForbidden('Invalid value for culprit')
-
- if not data.get('event_id'):
- data['event_id'] = uuid.uuid4().hex
- elif not isinstance(data['event_id'], six.string_types):
- raise APIForbidden('Invalid value for event_id')
-
- if len(data['event_id']) > 32:
- self.log.debug(
- 'Discarded value for event_id due to length (%d chars)', len(
- data['event_id'])
- )
- data['errors'].append(
- {
- 'type': EventError.VALUE_TOO_LONG,
- 'name': 'event_id',
- 'value': data['event_id'],
- }
- )
- data['event_id'] = uuid.uuid4().hex
- elif not is_event_id(data['event_id']):
- self.log.debug(
- 'Discarded invalid value for event_id: %r', data['event_id'], exc_info=True
- )
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'event_id',
- 'value': data['event_id'],
- }
- )
- data['event_id'] = uuid.uuid4().hex
-
- if 'timestamp' in data:
- try:
- self._process_data_timestamp(data)
- except InvalidTimestamp as e:
- self.log.debug(
- 'Discarded invalid value for timestamp: %r', data['timestamp'], exc_info=True
- )
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'timestamp',
- 'value': data['timestamp'],
- }
- )
- del data['timestamp']
-
- if 'fingerprint' in data:
- try:
- self._process_fingerprint(data)
- except InvalidFingerprint as e:
- self.log.debug(
- 'Discarded invalid value for fingerprint: %r',
- data['fingerprint'],
- exc_info=True
- )
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'fingerprint',
- 'value': data['fingerprint'],
- }
- )
- del data['fingerprint']
-
- if 'platform' not in data or data['platform'] not in VALID_PLATFORMS:
- data['platform'] = 'other'
-
- if data.get('modules') and type(data['modules']) != dict:
- self.log.debug(
- 'Discarded invalid type for modules: %s', type(data['modules']))
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'modules',
- 'value': data['modules'],
- }
- )
- del data['modules']
-
- if data.get('extra') is not None and type(data['extra']) != dict:
- self.log.debug('Discarded invalid type for extra: %s',
- type(data['extra']))
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'extra',
- 'value': data['extra'],
- }
- )
- del data['extra']
-
- if data.get('tags') is not None:
- if type(data['tags']) == dict:
- data['tags'] = list(data['tags'].items())
- elif not isinstance(data['tags'], (list, tuple)):
- self.log.debug(
- 'Discarded invalid type for tags: %s', type(data['tags']))
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'tags',
- 'value': data['tags'],
- }
- )
- del data['tags']
-
- if data.get('tags'):
- # remove any values which are over 32 characters
- tags = []
- for pair in data['tags']:
- try:
- k, v = pair
- except ValueError:
- self.log.debug('Discarded invalid tag value: %r', pair)
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'tags',
- 'value': pair,
- }
- )
- continue
-
- if not isinstance(k, six.string_types):
- try:
- k = six.text_type(k)
- except Exception:
- self.log.debug(
- 'Discarded invalid tag key: %r', type(k))
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'tags',
- 'value': pair,
- }
- )
- continue
-
- if not isinstance(v, six.string_types):
- try:
- v = six.text_type(v)
- except Exception:
- self.log.debug(
- 'Discarded invalid tag value: %s=%r', k, type(v))
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'tags',
- 'value': pair,
- }
- )
- continue
-
- if len(k) > MAX_TAG_KEY_LENGTH or len(v) > MAX_TAG_VALUE_LENGTH:
- self.log.debug('Discarded invalid tag: %s=%s', k, v)
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'tags',
- 'value': pair,
- }
- )
- continue
-
- # support tags with spaces by converting them
- k = k.replace(' ', '-')
-
- if tagstore.is_reserved_key(k):
- self.log.debug('Discarding reserved tag key: %s', k)
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'tags',
- 'value': pair,
- }
- )
- continue
-
- if not tagstore.is_valid_key(k):
- self.log.debug('Discarded invalid tag key: %s', k)
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'tags',
- 'value': pair,
- }
- )
- continue
-
- if not tagstore.is_valid_value(v):
- self.log.debug('Discard invalid tag value: %s', v)
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'tags',
- 'value': pair,
- }
- )
- continue
-
- tags.append((k, v))
- data['tags'] = tags
-
- for k in list(iter(data)):
- if k in CLIENT_RESERVED_ATTRS:
- continue
-
- value = data.pop(k)
-
- if not value:
- self.log.debug('Ignored empty interface value: %s', k)
- continue
-
- try:
- interface = get_interface(k)
- except ValueError:
- self.log.debug('Ignored unknown attribute: %s', k)
- data['errors'].append({
- 'type': EventError.INVALID_ATTRIBUTE,
- 'name': k,
- })
- continue
-
- if type(value) != dict:
- # HACK(dcramer): the exception/breadcrumbs interface supports a
- # list as the value. We should change this in a new protocol
- # version.
- if type(value) in (list, tuple):
- value = {'values': value}
- else:
- self.log.debug(
- 'Invalid parameter for value: %s (%r)', k, type(value))
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': k,
- 'value': value,
- }
- )
- continue
-
- try:
- inst = interface.to_python(value)
- data[inst.get_path()] = inst.to_json()
- except Exception as e:
- if isinstance(e, InterfaceValidationError):
- log = self.log.debug
- else:
- log = self.log.error
- log('Discarded invalid value for interface: %s (%r)',
- k, value, exc_info=True)
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': k,
- 'value': value,
- }
- )
-
- # TODO(dcramer): ideally this logic would happen in normalize, but today
- # we don't do "validation" there (create errors)
-
- # message is coerced to an interface, as its used for pure
- # index of searchable strings
- # See GH-3248
- message = data.pop('message', None)
- if message:
- if 'sentry.interfaces.Message' not in data:
- value = {
- 'message': message,
- }
- elif not data['sentry.interfaces.Message'].get('formatted'):
- value = data['sentry.interfaces.Message']
- value['formatted'] = message
- else:
- value = None
-
- if value is not None:
- k = 'sentry.interfaces.Message'
- interface = get_interface(k)
- try:
- inst = interface.to_python(value)
- data[inst.get_path()] = inst.to_json()
- except Exception as e:
- if isinstance(e, InterfaceValidationError):
- log = self.log.debug
- else:
- log = self.log.error
- log('Discarded invalid value for interface: %s (%r)',
- k, value, exc_info=True)
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': k,
- 'value': value,
- }
- )
-
- level = data.get('level') or DEFAULT_LOG_LEVEL
- if isinstance(level, six.string_types) and not level.isdigit():
- # assume it's something like 'warning'
- try:
- data['level'] = LOG_LEVELS_MAP[level]
- except KeyError as e:
- self.log.debug('Discarded invalid logger value: %s', level)
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'level',
- 'value': level,
- }
- )
- data['level'] = LOG_LEVELS_MAP.get(
- DEFAULT_LOG_LEVEL, DEFAULT_LOG_LEVEL)
-
- if data.get('release'):
- data['release'] = six.text_type(data['release'])
- if len(data['release']) > 64:
- data['errors'].append(
- {
- 'type': EventError.VALUE_TOO_LONG,
- 'name': 'release',
- 'value': data['release'],
- }
- )
- del data['release']
-
- if data.get('dist'):
- data['dist'] = six.text_type(data['dist']).strip()
- if not data.get('release'):
- data['dist'] = None
- elif len(data['dist']) > 64:
- data['errors'].append(
- {
- 'type': EventError.VALUE_TOO_LONG,
- 'name': 'dist',
- 'value': data['dist'],
- }
- )
- del data['dist']
- elif _dist_re.match(data['dist']) is None:
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'dist',
- 'value': data['dist'],
- }
- )
- del data['dist']
-
- if data.get('environment'):
- data['environment'] = six.text_type(data['environment'])
- if len(data['environment']) > 64:
- data['errors'].append(
- {
- 'type': EventError.VALUE_TOO_LONG,
- 'name': 'environment',
- 'value': data['environment'],
- }
- )
- del data['environment']
-
- if data.get('time_spent'):
- try:
- data['time_spent'] = int(data['time_spent'])
- except (ValueError, TypeError):
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'time_spent',
- 'value': data['time_spent'],
- }
- )
- del data['time_spent']
- else:
- if data['time_spent'] > BoundedIntegerField.MAX_VALUE:
- data['errors'].append(
- {
- 'type': EventError.VALUE_TOO_LONG,
- 'name': 'time_spent',
- 'value': data['time_spent'],
- }
- )
- del data['time_spent']
-
+ def validate_data(self, data):
return data
def ensure_does_not_have_ip(self, data):
@@ -819,26 +353,9 @@ def ensure_does_not_have_ip(self, data):
if 'sentry.interfaces.User' in data:
data['sentry.interfaces.User'].pop('ip_address', None)
- def ensure_has_ip(self, data, ip_address, set_if_missing=True):
- got_ip = False
- ip = data.get('sentry.interfaces.Http', {}) \
- .get('env', {}).get('REMOTE_ADDR')
- if ip:
- if ip == '{{auto}}':
- data['sentry.interfaces.Http']['env']['REMOTE_ADDR'] = ip_address
- got_ip = True
-
- ip = data.get('sentry.interfaces.User', {}).get('ip_address')
- if ip:
- if ip == '{{auto}}':
- data['sentry.interfaces.User']['ip_address'] = ip_address
- got_ip = True
-
- if not got_ip and set_if_missing:
- data.setdefault('sentry.interfaces.User', {})[
- 'ip_address'] = ip_address
-
- def insert_data_to_database(self, data, from_reprocessing=False):
+ def insert_data_to_database(self, data, start_time=None, from_reprocessing=False):
+ if start_time is None:
+ start_time = time()
# we might be passed LazyData
if isinstance(data, LazyData):
data = dict(data.items())
@@ -846,15 +363,35 @@ def insert_data_to_database(self, data, from_reprocessing=False):
default_cache.set(cache_key, data, timeout=3600)
task = from_reprocessing and \
preprocess_event_from_reprocessing or preprocess_event
- task.delay(cache_key=cache_key, start_time=time(),
+ task.delay(cache_key=cache_key, start_time=start_time,
event_id=data['event_id'])
-class CspApiHelper(ClientApiHelper):
+class MinidumpApiHelper(ClientApiHelper):
def origin_from_request(self, request):
# We don't use an origin here
return None
+ def auth_from_request(self, request):
+ key = request.GET.get('sentry_key')
+ if not key:
+ raise APIUnauthorized('Unable to find authentication information')
+
+ auth = Auth({'sentry_key': key}, is_public=True)
+ auth.client = 'sentry-minidump'
+ return auth
+
+
+class SecurityApiHelper(ClientApiHelper):
+
+ report_interfaces = ('sentry.interfaces.Csp', 'expectct', 'expectstaple')
+
+ def origin_from_request(self, request):
+ # In the case of security reports, the origin is not available at the
+ # dispatch() stage, as we need to parse it out of the request body, so
+ # we do our own CORS check once we have parsed it.
+ return None
+
def auth_from_request(self, request):
key = request.GET.get('sentry_key')
if not key:
@@ -869,101 +406,67 @@ def auth_from_request(self, request):
return auth
def should_filter(self, project, data, ip_address=None):
- if not is_valid_csp_report(data['sentry.interfaces.Csp'], project):
- return (True, FilterStatKeys.INVALID_CSP)
- return super(CspApiHelper, self).should_filter(project, data, ip_address)
+ for name in self.report_interfaces:
+ if name in data:
+ interface = get_interface(name)
+ if interface.to_python(data[name]).should_filter(project):
+ return (True, FilterStatKeys.INVALID_CSP)
- def validate_data(self, project, data):
- # pop off our meta data used to hold Sentry specific stuff
- meta = data.pop('_meta', {})
+ return super(SecurityApiHelper, self).should_filter(project, data, ip_address)
- # All keys are sent with hyphens, so we want to conver to underscores
- report = {k.replace('-', '_'): v for k, v in six.iteritems(data)}
+ def validate_data(self, data):
+ try:
+ interface = get_interface(data.pop('interface'))
+ report = data.pop('report')
+ except KeyError:
+ raise APIForbidden('No report or interface data')
+ # To support testing, we can either accept a buillt interface instance, or the raw data in
+ # which case we build the instance ourselves
try:
- inst = Csp.to_python(report)
- except Exception as exc:
- raise APIForbidden('Invalid CSP Report: %s' % exc)
-
- # Construct a faux Http interface based on the little information we have
- headers = {}
- if self.context.agent:
- headers['User-Agent'] = self.context.agent
- if inst.referrer:
- headers['Referer'] = inst.referrer
-
- data = {
+ instance = report if isinstance(report, interface) else interface.from_raw(report)
+ except jsonschema.ValidationError as e:
+ raise APIError('Invalid security report: %s' % str(e).splitlines()[0])
+
+ def clean(d):
+ return dict(filter(lambda x: x[1], d.items()))
+
+ data.update({
'logger': 'csp',
- 'project': project.id,
- 'message': inst.get_message(),
- 'culprit': inst.get_culprit(),
- 'release': meta.get('release'),
- inst.get_path(): inst.to_json(),
+ 'message': instance.get_message(),
+ 'culprit': instance.get_culprit(),
+ instance.get_path(): instance.to_json(),
+ 'tags': instance.get_tags(),
+ 'errors': [],
+
+ 'sentry.interfaces.User': {
+ 'ip_address': self.context.ip_address,
+ },
+
+ # Construct a faux Http interface based on the little information we have
# This is a bit weird, since we don't have nearly enough
# information to create an Http interface, but
# this automatically will pick up tags for the User-Agent
# which is actually important here for CSP
'sentry.interfaces.Http': {
- 'url': inst.document_uri,
- 'headers': headers,
- },
- 'sentry.interfaces.User': {
- 'ip_address': self.context.ip_address,
+ 'url': instance.get_origin(),
+ 'headers': clean({
+ 'User-Agent': self.context.agent,
+ 'Referer': instance.get_referrer(),
+ })
},
- 'errors': [],
- }
-
- # Copy/pasted from above in ClientApiHelper.validate_data
- if data.get('release'):
- data['release'] = six.text_type(data['release'])
- if len(data['release']) > 64:
- data['errors'].append(
- {
- 'type': EventError.VALUE_TOO_LONG,
- 'name': 'release',
- 'value': data['release'],
- }
- )
- del data['release']
-
- tags = []
- for k, v in inst.get_tags():
- if not v:
- continue
- if len(v) > MAX_TAG_VALUE_LENGTH:
- self.log.debug('Discarded invalid tag: %s=%s', k, v)
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'tags',
- 'value': (k, v),
- }
- )
- continue
- if not tagstore.is_valid_value(v):
- self.log.debug('Discard invalid tag value: %s', v)
- data['errors'].append(
- {
- 'type': EventError.INVALID_DATA,
- 'name': 'tags',
- 'value': (k, v),
- }
- )
- continue
- tags.append((k, v))
-
- if tags:
- data['tags'] = tags
+ })
return data
class LazyData(MutableMapping):
- def __init__(self, data, content_encoding, helper, project, auth, client_ip):
+ def __init__(self, data, content_encoding, helper, project, key, auth, client_ip):
self._data = data
self._content_encoding = content_encoding
self._helper = helper
self._project = project
+ self._key = key
self._auth = auth
self._client_ip = client_ip
self._decoded = False
@@ -972,7 +475,6 @@ def _decode(self):
data = self._data
content_encoding = self._content_encoding
helper = self._helper
- project = self._project
auth = self._auth
# TODO(dcramer): CSP is passing already decoded JSON, which sort of
@@ -995,29 +497,18 @@ def _decode(self):
# version of the data
# mutates data
- data = helper.validate_data(project, data)
+ data = helper.validate_data(data)
- if 'sdk' not in data:
- sdk = helper.parse_client_as_sdk(auth.client)
- if sdk:
- data['sdk'] = sdk
- else:
- data['sdk'] = {}
-
- data['sdk']['client_ip'] = self._client_ip
-
- # we always fill in the IP so that filters and other items can
- # access it (even if it eventually gets scrubbed)
- helper.ensure_has_ip(
- data,
- self._client_ip,
- set_if_missing=auth.is_public or
- data.get('platform') in ('javascript', 'cocoa', 'objc')
- )
+ data['project'] = self._project.id
+ data['key_id'] = self._key.id
+ data['sdk'] = data.get('sdk') or helper.parse_client_as_sdk(auth.client)
# mutates data
manager = EventManager(data, version=auth.version)
- manager.normalize()
+ manager.normalize(request_env={
+ 'client_ip': self._client_ip,
+ 'auth': self._auth,
+ })
self._data = data
self._decoded = True
diff --git a/src/sentry/data/error-locale/ar-SA.txt b/src/sentry/data/error-locale/ar-SA.txt
new file mode 100644
index 00000000000000..9e4ed77aed8c3a
--- /dev/null
+++ b/src/sentry/data/error-locale/ar-SA.txt
@@ -0,0 +1,289 @@
+5,استدعاء إجراء غير صالح أو وسيطة غير صالحة
+6,تجاوز سعة
+7,نفاد الذاكرة
+9,رمز سفلي خارج النطاق
+10,هذا الصفيف ثابت أو أنه مؤمن مؤقتاً
+11,تقسيم على صفر
+13,عدم تطابق في النوع
+14,نفاد مساحة السلسلة
+17,لا يمكن إنجاز العملية المطلوبة
+28,نفاد مساحة المكدس
+35,إجراء فرعي أو دالة غير معرّفين
+48,خطأ في تحميل DLL
+51,خطأ داخلي
+52,اسم ملف أو رقم غير صحيحين
+53,لم يتم العثور على الملف
+54,وضع ملف غير صحيح
+55,الملف مفتوح مسبقاً
+57,خطأ في إدخال/إخراج جهاز
+58,الملف موجود مسبقاً
+61,القرص ممتلئ
+62,تجاوز الإدخال نهاية الملف
+67,عدد ملفات كبير جداً
+68,الجهاز غير متوفر
+70,الإذن مرفوض
+71,القرص غير جاهز
+74,لا يمكن إعادة التسمية إلى محرك أقراص مختلف
+75,خطأ في الوصول إلى المسار/الملف
+76,لم يتم العثور على المسار
+91,متغير غير معين من نوع Object أو من نوع With block
+92,حلقة For غير مهيئة
+94,استخدام غير صالح للقيمة الفارغة Null
+322,لا يمكن إنشاء ملف مؤقت ضروري
+424,مطلوب كائن (Object)
+429,لا يمكن لملقم الأتمتة إنشاء كائن
+430,الفئة (Class) لا تعتمد الأتمتة (Automation)
+432,لم يتم العثور على اسم الملف أو اسم الفئة خلال عملية الأتمتة (Automation)
+438,هذه الخاصية أو هذا الأسلوب غير معتمدين من قبل الكائن
+440,خطأ أتمتة (Automation)
+445,هذا الإجراء غير معتمد من قبل الكائن
+446,الوسائط المسماة غير معتمدة من قبل الكائن
+447,إعداد الموقع الحالي غير معتمد من قبل الكائن
+448,لم يتم العثور على الوسيطة المسماة
+449,الوسيطة ليست اختيارية
+450,عدد وسائط غير صحيح أو تعيين خاصية غير صالح
+451,الكائن ليس بمجموعة
+453,لم يتم العثور على دالة DLL المحددة
+458,المتغيرات التي تستخدم النوع Automation غير معتمدة في JavaScript
+462,جهاز الملقم البعيد غير موجود أو غير متوفر
+501,لا يمكن التعيين إلى متغير
+502,الكائن ليس آمناً لاستخدامه في البرمجة النصية
+503,الكائن غير آمن لتهيئته
+504,الكائن غير آمن لإنشائه
+507,حدث استثناء حاسم
+1001,نفاد الذاكرة
+1002,خطأ في بناء الجملة
+1003,المتوقع وجود ':'
+1004,المتوقع وجود ';'
+1005,المتوقع وجود ')'
+1006,المتوقع وجود '('
+1007,المتوقع وجود '['
+1008,المتوقع وجود '}'
+1009,المتوقع وجود '{'
+1010,المتوقع وجود معرّف
+1011,المتوقع وجود '='
+1012,المتوقع وجود '/'
+1013,رقم غير صالح
+1014,حرف غير صالح
+1015,ثابت سلسلة غير منهي
+1016,تعليق غير منهي
+1018,عبارة 'return' خارج الدالة
+1019,لا يمكن وجود 'break' خارج الحلقة
+1020,لا يمكن وجود 'continue' خارج الحلقة
+1023,المتوقع وجود رقم ست عشري
+1024,المتوقع وجود 'while'
+1025,عنوان (Label) تمت إعادة تعريفه
+1026,عنوان (Label) غير موجود
+1027,لا يمكن ظهور 'default' أكثر من مرة واحدة في عبارة 'switch'
+1028,المتوقع وجود معرّف، أو سلسلة، أو رقم
+1029,المتوقع وجود '@end'
+1030,الترجمة الشرطية بحالة عدم تشغيل
+1031,المتوقع وجود ثابت
+1032,المتوقع وجود '@'
+1033,المتوقع وجود 'catch'
+1034,المتوقع وجود 'var'
+1035,يجب أن تأتي 'throw' متبوعة بتعبير في سطر المصدر نفسه
+1037,تكون عبارات 'with' غير مسموح بها في الوضع المقيد
+1038,غير مسموح بأسماء المعلمات الرسمية المكررة في الوضع المقيد
+1039,أحرف الأرقام الثُمانية وأحرف الإلغاء غير مسموح بها في الوضع المقيد
+1041,استخدام غير صالح لـ 'eval' في الوضع المقيد
+1042,استخدام غير صالح لـ 'arguments' في الوضع المقيد
+1045,غير مسموح بطلب الحذف في التعبير في الوضع المقيد
+1046,غير مسموح بتعريفات متعددة للخاصية في الوضع المقيد
+1047,في الوضع المقيّد، يتعذر تضمين إقرارات الدوال في بيان أو كتلة. يمكن لهذه الإقرارات أن تظهر فقط في المستوى الأعلى أو مباشرةً داخل نص دالة.
+1048,يُعد استخدام كلمة أساسية لمعرف إجراءً غير صالح
+1049,يُعد استخدام كلمة محجوزة مستقبلاً لمعرف إجراءً غير صالح
+1050,يُعد استخدام كلمة محجوزة مستقبلاً لمعرف إجراءً غير صالح. يتم حفظ اسم المعرف في الوضع المقيّد.
+1051,يلزم أن يكون لوظائف Setter وسيطة واحدة
+4096,خطأ تحويل برمجي في JavaScript
+4097,خطأ وقت التشغيل في JavaScript
+4098,خطأ أثناء وقت التشغيل غير معروف
+5000,لا يمكن التعيين إلى 'this'
+5001,المتوقع وجود رقم
+5002,المتوقع وجود دالة
+5003,لا يمكن التعيين إلى ناتج دالة
+5004,لا يمكن فهرسة كائن
+5005,المتوقع وجود سلسلة
+5006,المتوقع وجود كائن من نوع Date
+5007,المتوقع وجود كائن
+5008,تعيين غير صالح جهة اليمين
+5009,معرّف لم يتم تعريفه
+5010,المتوقع وجود Boolean
+5011,لا يمكن تنفيذ تعليمات من برنامج نصي ذي نوع freed
+5012,المتوقع وجود عضو كائن
+5013,المتوقع وجود VBArray
+5014,كائن JavaScript متوقع
+5015,المتوقع وجود كائن من نوع Enumerator
+5016,المتوقع وجود كائن من نوع Regular Expression
+5017,خطأ في بناء الجملة ضمن تعبير عادي
+5018,مقدار غير متوقع
+5019,المتوقع وجود ']' ضمن تعبير عادي
+5020,المتوقع وجود ')' ضمن تعبير عادي
+5021,نطاق غير صالح ضمن مجموعة أحرف
+5022,استثناء إهمال وليس إمساك
+5023,لا تتضمن الدالة كائناً صالحاً من نوع prototype
+5024,URI المراد فك ترميزها تحتوي على حرف غير صالح
+5025,URI المراد فك ترميزها لها ترميز غير صالح
+5026,عدد الأرقام الكسرية خارج النطاق
+5027,الدقة خارج النطاق
+5028,المتوقع وجود كائن صفيف أو وسائط
+5029,يجب أن يكون طول الصفيف عدد صحيح موجب منتهي
+5030,يجب أن يتم تعيين عدد موجب منتهي لطول الصفيف
+5031,المتوقع وجود كائن صفيف
+5034,المرجع الدائري في وسيطة القيمة غير مدعوم
+5035,وسيطة بديلة غير صالحة
+5038,قائمة الوسيطات كبيرة جداً بحيث يصعب تطبيقها
+5039,إعادة تعريف خاصية ثابتة
+5041,يتعذر تكوين عضو الكائن
+5042,المتغير غير معرّف في الوضع المقيد
+5043,لا يُسمح بالوصول إلى خاصية 'المتصل' لدالة أو كائن وسيطات في الوضع المقيد
+5044,لا يُسمح بالوصول إلى خاصية 'المتصَل به' لدالة أو كائن وسيطات في الوضع المقيد
+5045,في الوضع المقيد، غير مسموح بالتعيين إلى خصائص للقراءة فقط
+5046,يتعذر إنشاء خاصية لكائن غير قابل للامتداد
+5047,كائن من نوع Object متوقع
+5048,كائن من نوع Object متوقع
+5049,كائن من نوع Object متوقع
+5050,كائن من نوع Object متوقع
+5051,كائن من نوع Function متوقع
+5052,كائن من نوع Function متوقع
+5053,يتعذر أن يكون للخاصية كلا الموصلين وقيمة
+5054,'this' خالية أو غير معرّفة
+5055,كائن من نوع Object متوقع
+5056,كائن من نوع Function متوقع
+5057,كائن من نوع String متوقع
+5058,كائن من نوع Boolean متوقع
+5059,التاريخ المتوقع
+5060,كائن من نوع Number متوقع
+5061,كائن من نوع VBArray متوقع
+5062,كائن JavaScript متوقع
+5063,كائن من نوع Enumerator متوقع
+5064,كائن RegExp متوقع
+5065,وسيطة وظيفة غير صالحة
+5066,كائن من نوع Object متوقع
+5067,كائن JavaScript متوقع
+5068,كائن من نوع Function متوقع
+5069,كائن من نوع VBArray متوقع
+5070,كائن من نوع Object متوقع
+5071,كائن من نوع Object متوقع
+5072,الخاصية 'length' غير صالحة
+5073,المتوقع وجود كائن صفيف أو وسائط
+5074,مُعامل غير صالح
+5075,مُعامل غير صالح
+5076,واصف خاصية غير صالح
+5077,يتعذر تحديد الخاصية: الكائن غير قابل للمد
+5078,يتعذر إعادة تعريف الخاصية غير القابلة للتكوين
+5079,يتعذر تعديل الخاصية غير القابلة للكتابة
+5080,يتعذر تعديل الخاصية: 'length' غير قابلة للكتابة
+5081,يتعذر تحديد الخاصية
+5082,وسيطة منشئ الصفيف المكتوب غير صالحة
+5083,'هذا' ليس كائن صفيف مكتوب
+5084,الإزاحة/الطول غير صالح عند إنشاء صفيف مكتوب
+5085,قيمة بداية/انتهاء غير صالحة بأسلوب الصفيف الفرعي المكتوب
+5086,مصدر غير صالح في مجموعة الصفيف المكتوب
+5087,'هذا' ليس كائن عرض بيانات
+5088,وسيطات غير صالحة بعرض البيانات
+5089,وصول تشغيل عرض البيانات يتجاوز طول المخزن المؤقت المحدد
+5090,وسيطات غير صالحة بعرض البيانات
+5091,توقيع الوظيفة غير صالح
+5092,توقيع الخاصية غير صالح
+5093,نوع معلمة إدخال غير صالحة
+5094,معلمة إدخال غير صالحة
+5095,لا يُسمح بالوصول إلى خاصية 'الوسيطات' لدالة في الوضع المقيد
+5096,تم توقع كائن يمكن فحصه
+5097,تعذر تحويل وسيطة لكتابة 'الحرف'
+5098,تعذر تحويل وسيطة لكتابة 'المعرف الفريد العمومي'
+5099,IInspectable متوقع
+5100,تعذر تحويل كائن إلى بنية: يفقد الكائن خاصية متوقعة
+5101,نوع غير معروف
+5102,تم استدعاء الوظيفة باستخدام وسيطات قليلة جداً
+5103,الكتابة غير قابلة للبناء
+5104,تعذر تحويل القيمة إلى PropertyValue: PropertyValue لا يدعم الكتابة
+5105,تعذر تحويل القيمة إلى IInspectable: IInspectable لا يدعم الكتابة
+5106,تعذر تحويل التاريخ إلى Windows.Foundation.DateTime: القيمة خارج النطاق الصحيح
+5107,تعذر تحويل القيمة إلى Windows.Foundation.TimeSpan: القيمة خارج النطاق الصحيح
+5108,وصول غير صالح إلى كائن قابل للفحص تم تحريره بالفعل
+5109,يتعذر تحرير كائن قابل للفحص تم تحريره بالفعل
+5110,'هذا' ليس من النوع المتوقع
+5111,تم تحديد حجم وطول غير قانوني للصفيف
+5112,حدث فشل غير متوقع أثناء محاولة الحصول على معلومات بيانات التعريف
+5200,الحالة "خطأ"، لكن لم يُرجع getResults خطأً
+5201,تم تمرير معلمة حالة مفقودة أو غير صالحة للمعالج المكتمل
+5202,تم تمرير معلمة مُرسل مفقودة أو غير صالحة للمعالج المكتمل
+6000,ما لا نهاية
+6001,-ما لا نهاية
+10438,Object doesn't support property or method '%s'
+10449,وسيطة الوظيفة '%s' ليست اختيارية
+15001,'%s' ليس رقماً
+15002,'%s' ليس دالة
+15004,'%s' ليس كائناً قابلاً للفهرسة
+15005,'%s' ليس سلسلة
+15006,'%s' ليس كائناً من نوع date
+15007,'%s' فارغاً أو ليس كائناً
+15008,يتعذر التعيين إلى '%s'
+15009,'%s' غير محدد
+15010,'%s' ليس منطقياً
+15012,يتعذر حذف '%s'
+15013,'%s' هو ليس VBArray
+15014,'%s' ليس كائن JavaScript
+15015,'%s' ليس كائناً تعدادياً
+15016,'%s' ليس كائن تعبير عادي
+15028,%s ليس كائن صفيف أو وسائط
+15031,%s ليس كائن صفيف
+15036,يتعذر تعيين السمة '%s' في واصف الخاصية إلى 'true' في هذا الكائن
+15037,يتعذر تعيين السمة '%s' في واصف الخاصية إلى 'false' في هذا الكائن
+15039,إعادة تعريف خاصية '%s' ثابتة
+15041,غير مسموح بطلب الحذف في الخاصية '%s' في الوضع المقيد
+15047,تعذر تعيين الخاصية '%s' لمرجع غير محدد أو فارغ
+15048,Unable to get property '%s' of undefined or null reference
+15049,تعذر حذف الخاصية '%s' لمرجع غير محدد أو فارغ
+15050,تعذر الوصول إلى الخاصية '%s': النوع 'VarDate' لا يدعم الخصائص غير المحددة بواسطة المستخدم
+15051,قيمة الخاصية '%s' ليست كائن وظيفة
+15052,قيمة الخاصية '%s' خالية أو غير معرفة وهي ليست كائن وظيفة
+15054,%s: الخاصية 'this' خالية أو غير محددة
+15055,%s: الخاصية 'this' ليست من النوع Object
+15056,%s:الخاصية 'this' ليست كائن من النوع Function
+15057,%s: الخاصية 'this' ليست كائن من النوع String
+15058,%s: الخاصية 'this' ليست كائن من النوع Boolean
+15059,%s: الخاصية 'this' ليست كائن من النوع Date
+15060,%s: الخاصية 'this' ليست كائن من النوع Number
+15061,%s: الخاصية 'this' ليست كائن من النوع VBArray
+15062,%s: الخاصية 'this' ليست كائن من النوع JavaScript
+15063,%s: الخاصية 'this' ليست كائن من النوع Enumerator
+15064,%s: الخاصية 'this' ليست كائن من النوع RegExp
+15065,%s: وسيطة غير صالحة
+15066,%s: وسيطة ليست من النوع Object
+15067,%s: الوسيطة ليست كائن JavaScript
+15068,%s: الوسيطة ليست كائن Function
+15069,%s: الوسيطة ليست كائن VBArray
+15070,%s: وسيطة خالية أو غير محددة
+15071,%s: وسيطة ليست Object وليست خالية
+15072,%s: الوسيطة لا تحتوي على خاصية 'length' صالحة
+15073,%s: كائن صفيف أو وسيطات متوقع
+15074,مُعامل غير صالح لـ'%s': نوع Object متوقع
+15075,مُعامل غير صالح لـ '%s' نوع Function متوقع
+15076,واصف غير صالح للخاصية '%s'
+15077,يتعذر تعريف الخاصية '%s': الكائن غير قابل للمد
+15078,يتعذر إعادة تعريف الخاصية غير القابلة للتكوين '%s'
+15079,يتعذر تعديل الخاصية غير القابلة للكتابة '%s'
+15080,يتعذر تعديل الخاصية '%s': 'length' غير قابل للكتابة
+15081,يتعذر إعادة تعريف الخاصية '%s'
+15088,لم يتم تحديد الوسيطة المطلوبة %s بأسلوب عرض البيانات
+15090,وسيطة منشئ عرض البيانات %s غير صالحة
+15091,تحتوي الدالة '%s' على توقيع غير صالح ولا يمكن استدعاؤها
+15092,تحتوي الخاصية '%s' على توقيع غير صالح ولا يمكن الوصول إليها
+15093,لا يتم دعم فئة وقت التشغيل %s، التي تتضمن Windows.Foundation.IPropertyValue كواجهة افتراضية، كنوع معلمة إدخال
+15094,لا يتم دعم الكائن، الذي يحتوي على الواجهة Windows.Foundation.IPropertyValue التي تتضمن اسم فئة التشغيل %s، كمعلمة إخراج
+15096,%s: "هذا" ليس كائن قابل للفحص
+15097,%s: تعذر تحويل وسيطة لكتابة "الحرف"
+15098,%s: تعذر تحويل وسيطة لكتابة "المعرف الفريد العمومي"
+15099,%s: تعذر تحويل قيمة الإرجاع إلى IInspectable
+15100,تعذر تحويل كائن إلى بنية: يفقد الكائن خاصية متوقعة '%s'
+15101,لم يتم العثور على النوع '%s'
+15102,%s: تم استدعاء الوظيفة باستخدام وسيطات قليلة جداً
+15103,%s: الكتابة غير قابلة للبناء
+15104,تعذر تحويل القيمة إلى PropertyValue: PropertyValue غير مدعومة من قبل %s
+15105,تعذر تحويل القيمة إلى IInspectable: IInspectable لا يدعم %s
+15108,%s: تم تحرير الكائن "هذا" القابل للفحص، كما يتعذر الوصول إليه
+15110,'هذا' ليس من النوع المتوقع: %s
+15112,%s: حدث فشل غير متوقع أثناء محاولة الحصول على معلومات بيانات التعريف
+32812,التاريخ المحدد غير متوفر في تقويم الموقع الحالي
diff --git a/src/sentry/data/error-locale/bg-BG.txt b/src/sentry/data/error-locale/bg-BG.txt
new file mode 100644
index 00000000000000..6580983d9b4a88
--- /dev/null
+++ b/src/sentry/data/error-locale/bg-BG.txt
@@ -0,0 +1,289 @@
+5,Invalid procedure call or argument
+6,Overflow
+7,Out of memory
+9,Subscript out of range
+10,This array is fixed or temporarily locked
+11,Division by zero
+13,Type mismatch
+14,Out of string space
+17,Can't perform requested operation
+28,Out of stack space
+35,Sub or Function not defined
+48,Error in loading DLL
+51,Internal error
+52,Bad file name or number
+53,File not found
+54,Bad file mode
+55,File already open
+57,Device I/O error
+58,File already exists
+61,Disk full
+62,Input past end of file
+67,Too many files
+68,Device unavailable
+70,Permission denied
+71,Disk not ready
+74,Can't rename with different drive
+75,Path/File access error
+76,Path not found
+91,Object variable or With block variable not set
+92,For loop not initialized
+94,Invalid use of Null
+322,Can't create necessary temporary file
+424,Object required
+429,Automation server can't create object
+430,Class doesn't support Automation
+432,File name or class name not found during Automation operation
+438,Object doesn't support this property or method
+440,Automation error
+445,Object doesn't support this action
+446,Object doesn't support named arguments
+447,Object doesn't support current locale setting
+448,Named argument not found
+449,Argument not optional
+450,Wrong number of arguments or invalid property assignment
+451,Object not a collection
+453,Specified DLL function not found
+458,Variable uses an Automation type not supported in JavaScript
+462,The remote server machine does not exist or is unavailable
+501,Cannot assign to variable
+502,Object not safe for scripting
+503,Object not safe for initializing
+504,Object not safe for creating
+507,An exception occurred
+1001,Out of memory
+1002,Syntax error
+1003,Expected ':'
+1004,Expected ';'
+1005,Expected '('
+1006,Expected ')'
+1007,Expected ']'
+1008,Expected '{'
+1009,Expected '}'
+1010,Expected identifier
+1011,Expected '='
+1012,Expected '/'
+1013,Invalid number
+1014,Invalid character
+1015,Unterminated string constant
+1016,Unterminated comment
+1018,'return' statement outside of function
+1019,Can't have 'break' outside of loop
+1020,Can't have 'continue' outside of loop
+1023,Expected hexadecimal digit
+1024,Expected 'while'
+1025,Label redefined
+1026,Label not found
+1027,'default' can only appear once in a 'switch' statement
+1028,Expected identifier, string or number
+1029,Expected '@end'
+1030,Conditional compilation is turned off
+1031,Expected constant
+1032,Expected '@'
+1033,Expected 'catch'
+1034,Expected 'var'
+1035,'throw' must be followed by an expression on the same source line
+1037,'with' statements are not allowed in strict mode
+1038,Duplicate formal parameter names not allowed in strict mode
+1039,Octal numeric literals and escape characters not allowed in strict mode
+1041,Invalid usage of 'eval' in strict mode
+1042,Invalid usage of 'arguments' in strict mode
+1045,Calling delete on expression not allowed in strict mode
+1046,Multiple definitions of a property not allowed in strict mode
+1047,In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.
+1048,The use of a keyword for an identifier is invalid
+1049,The use of a future reserved word for an identifier is invalid
+1050,The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.
+1051,Setter functions must have one argument
+4096,JavaScript compilation error
+4097,JavaScript runtime error
+4098,Unknown runtime error
+5000,Cannot assign to 'this'
+5001,Number expected
+5002,Function expected
+5003,Cannot assign to a function result
+5004,Cannot index object
+5005,String expected
+5006,Date object expected
+5007,Object expected
+5008,Invalid left-hand side in assignment
+5009,Undefined identifier
+5010,Boolean expected
+5011,Can't execute code from a freed script
+5012,Object member expected
+5013,VBArray expected
+5014,JavaScript object expected
+5015,Enumerator object expected
+5016,Regular Expression object expected
+5017,Syntax error in regular expression
+5018,Unexpected quantifier
+5019,Expected ']' in regular expression
+5020,Expected ')' in regular expression
+5021,Invalid range in character set
+5022,Exception thrown and not caught
+5023,Function does not have a valid prototype object
+5024,The URI to be encoded contains an invalid character
+5025,The URI to be decoded is not a valid encoding
+5026,The number of fractional digits is out of range
+5027,The precision is out of range
+5028,Array or arguments object expected
+5029,Array length must be a finite positive integer
+5030,Array length must be assigned a finite positive number
+5031,Array object expected
+5034,Circular reference in value argument not supported
+5035,Invalid replacer argument
+5038,Argument list too large to apply
+5039,Redeclaration of const property
+5041,Object member not configurable
+5042,Variable undefined in strict mode
+5043,Accessing the 'caller' property of a function or arguments object is not allowed in strict mode
+5044,Accessing the 'callee' property of an arguments object is not allowed in strict mode
+5045,Assignment to read-only properties is not allowed in strict mode
+5046,Cannot create property for a non-extensible object
+5047,Object expected
+5048,Object expected
+5049,Object expected
+5050,Object expected
+5051,Function expected
+5052,Function expected
+5053,Property cannot have both accessors and a value
+5054,'this' is null or undefined
+5055,Object expected
+5056,Function expected
+5057,String expected
+5058,Boolean expected
+5059,Date expected
+5060,Number expected
+5061,VBArray expected
+5062,JavaScript object expected
+5063,Enumerator object expected
+5064,RegExp object expected
+5065,Invalid function argument
+5066,Object expected
+5067,JavaScript object expected
+5068,Function expected
+5069,VBArray expected
+5070,Object expected
+5071,Object expected
+5072,Invalid 'length' property
+5073,Array or arguments object expected
+5074,Invalid Operand
+5075,Invalid Operand
+5076,Invalid property descriptor
+5077,Cannot define property: object is not extensible
+5078,Cannot redefine non-configurable property
+5079,Cannot modify non-writable property
+5080,Cannot modify property: 'length' is not writable
+5081,Cannot define property
+5082,Typed array constructor argument is invalid
+5083,'this' is not a typed array object
+5084,Invalid offset/length when creating typed array
+5085,Invalid begin/end value in typed array subarray method
+5086,Invalid source in typed array set
+5087,'this' is not a DataView object
+5088,Invalid arguments in DataView
+5089,DataView operation access beyond specified buffer length
+5090,Invalid arguments in DataView
+5091,invalid function signature
+5092,invalid property signature
+5093,invalid input parameter type
+5094,invalid ouput parameter
+5095,Accessing the 'arguments' property of a function is not allowed in strict mode
+5096,Inspectable Object expected
+5097,Could not convert argument to type 'char'
+5098,Could not convert argument to type 'GUID'
+5099,IInspectable expected
+5100,Could not convert object to struct: object missing expected property
+5101,Unknown type
+5102,Function called with too few arguments
+5103,Type is not constructible
+5104,Could not convert value to PropertyValue: Type not supported by PropertyValue
+5105,Could not convert value to IInspectable: Type not supported by IInspectable
+5106,Could not convert Date to Windows.Foundation.DateTime: value outside of valid range
+5107,Could not convert value to Windows.Foundation.TimeSpan: value outside of valid range
+5108,Invalid access to already released Inspectable Object
+5109,Cannot release already released Inspectable Object
+5110,'this' is not of the expected type
+5111,Illegal length and size specified for the array
+5112,An unexpected failure occurred while trying to obtain metadata information
+5200,Status is 'error', but getResults did not return an error
+5201,Missing or invalid status parameter passed to completed handler
+5202,Missing or invalid sender parameter passed to completed handler
+6000,Infinity
+6001,-Infinity
+10438,Object doesn't support property or method '%s'
+10449,Argument to the function '%s' is not optional
+15001,'%s' is not a number
+15002,'%s' is not a function
+15004,'%s' is not an indexable object
+15005,'%s' is not a string
+15006,'%s' is not a date object
+15007,'%s' is null or not an object
+15008,Cannot assign to '%s'
+15009,'%s' is undefined
+15010,'%s' is not a boolean
+15012,Cannot delete '%s'
+15013,'%s' is not a VBArray
+15014,'%s' is not a JavaScript object
+15015,'%s' is not an enumerator object
+15016,'%s' is not a regular expression object
+15028,%s is not an Array or arguments object
+15031,%s is not an Array object
+15036,'%s' attribute on the property descriptor cannot be set to 'true' on this object
+15037,'%s' attribute on the property descriptor cannot be set to 'false' on this object
+15039,Redeclaration of const '%s'
+15041,Calling delete on '%s' is not allowed in strict mode
+15047,Unable to set property '%s' of undefined or null reference
+15048,Unable to get property '%s' of undefined or null reference
+15049,Unable to delete property '%s' of undefined or null reference
+15050,Unable to access property '%s': type 'VarDate' does not support user-defined properties
+15051,The value of the property '%s' is not a Function object
+15052,The value of the property '%s' is null or undefined, not a Function object
+15054,%s: 'this' is null or undefined
+15055,%s: 'this' is not an Object
+15056,%s: 'this' is not a Function object
+15057,%s: 'this' is not a String object
+15058,%s: 'this' is not a Boolean object
+15059,%s: 'this' is not a Date object
+15060,%s: 'this' is not a Number object
+15061,%s: 'this' is not a VBArray object
+15062,%s: 'this' is not a JavaScript object
+15063,%s: 'this' is not an Enumerator object
+15064,%s: 'this' is not a RegExp object
+15065,%s: invalid argument
+15066,%s: argument is not an Object
+15067,%s: argument is not a JavaScript object
+15068,%s: argument is not a Function object
+15069,%s: argument is not a VBArray object
+15070,%s: argument is null or undefined
+15071,%s: argument is not an Object and is not null
+15072,%s: argument does not have a valid 'length' property
+15073,%s: Array or arguments object expected
+15074,Invalid operand to '%s': Object expected
+15075,Invalid operand to '%s': Function expected
+15076,Invalid descriptor for property '%s'
+15077,Cannot define property '%s': object is not extensible
+15078,Cannot redefine non-configurable property '%s'
+15079,Cannot modify non-writable property '%s'
+15080,Cannot modify property '%s': 'length' is not writable
+15081,Cannot define property '%s'
+15088,Required argument %s in DataView method is not specified
+15090,DataView constructor argument %s is invalid
+15091,The function '%s' has an invalid signature and cannot be called
+15092,The property '%s' has an invalid signature and cannot be accessed
+15093,The runtimeclass %s that has Windows.Foundation.IPropertyValue as default interface is not supported as input parameter type
+15094,The object with interface Windows.Foundation.IPropertyValue that has runtimeclass name %s is not supported as out parameter
+15096,%s: 'this' is not an Inspectable Object
+15097,%s: could not convert argument to type 'char'
+15098,%s: could not convert argument to type 'GUID'
+15099,%s: could not convert return value to IInspectable
+15100,Could not convert object to struct: object missing expected property '%s'
+15101,Type '%s' not found
+15102,%s: function called with too few arguments
+15103,%s: type is not constructible
+15104,Could not convert value to PropertyValue: %s not supported by PropertyValue
+15105,Could not convert value to IInspectable: %s not supported by IInspectable
+15108,%s: The Inspectable object 'this' is released and cannot be accessed
+15110,'this' is not of expected type: %s
+15112,%s: an unexpected failure occurred while trying to obtain metadata information
+32812,The specified date is not available in the current locale's calendar
diff --git a/src/sentry/data/error-locale/cs-CZ.txt b/src/sentry/data/error-locale/cs-CZ.txt
new file mode 100644
index 00000000000000..dc2efb2e7694f7
--- /dev/null
+++ b/src/sentry/data/error-locale/cs-CZ.txt
@@ -0,0 +1,289 @@
+5,Neplatné volání nebo argument procedury
+6,Přetečení
+7,Nedostatek paměti
+9,Index prvku pole je mimo rozsah.
+10,Pole je pevné nebo dočasně uzamčené.
+11,Dělení nulou
+13,Neshoda typu
+14,Nedostatek místa v řetězci
+17,Nelze provést požadovanou operaci.
+28,Nedostatek místa v zásobníku
+35,Procedura Sub nebo funkce Function není definována.
+48,Chyba při načítání knihovny DLL
+51,Vnitřní chyba
+52,Chybný název nebo číslo souboru
+53,Soubor nebyl nalezen.
+54,Chybný režim souboru
+55,Soubor je již otevřen.
+57,Vstupně-výstupní chyba zařízení
+58,Soubor již existuje.
+61,Disk je plný.
+62,Vstup za koncem souboru
+67,Příliš mnoho souborů
+68,Zařízení není k dispozici.
+70,Oprávnění byla odepřena.
+71,Disk není připraven.
+74,Nelze přejmenovat na jinou jednotku.
+75,Chyba přístupu k souboru nebo k cestě
+76,Cesta nebyla nalezena.
+91,Proměnná typu Object nebo blok With není nastavena.
+92,Smyčka For není inicializována.
+94,Neplatné použití položky Null
+322,Nelze vytvořit potřebný dočasný soubor.
+424,Je požadován objekt.
+429,Automatizační server nemůže vytvořit objekt.
+430,Třída nepodporuje objekt Automation.
+432,Název souboru nebo třídy nebyl během operace s objektem Automation nalezen.
+438,Objekt tuto vlastnost nebo metodu nepodporuje.
+440,Chyba automatizace
+445,Objekt tuto akci nepodporuje.
+446,Objekt nepodporuje pojmenované argumenty.
+447,Objekt nepodporuje aktuální místní nastavení.
+448,Pojmenovaný argument nebyl nalezen.
+449,Argument není nepovinný.
+450,Chybný počet argumentů nebo chybné přiřazení vlastnosti
+451,Objekt není kolekce.
+453,Zadaná funkce knihovny DLL nebyla nalezena.
+458,Proměnná používá typ Automation, který není v jazyce JavaScript podporován.
+462,Vzdálený server neexistuje nebo není k dispozici.
+501,Nelze přiřadit do proměnné.
+502,Skriptování objektu není bezpečné.
+503,Inicializace objektu není bezpečná.
+504,Vytvoření objektu není bezpečné.
+507,Došlo k výjimce.
+1001,Nedostatek paměti
+1002,Chybná syntaxe
+1003,Byl očekáván znak ':'
+1004,Byl očekáván znak ';'
+1005,Byl očekáván znak '('
+1006,Byl očekáván znak ')'
+1007,Byl očekáván znak ']'
+1008,Byl očekáván znak '{'
+1009,Byl očekáván znak '}'
+1010,Byl očekáván identifikátor.
+1011,Byl očekáván znak '='
+1012,Byl očekáván znak '/'
+1013,Neplatné číslo
+1014,Neplatný znak
+1015,Neukončená řetězcová konstanta
+1016,Neukončený komentář
+1018,Příkaz 'return' byl použit vně funkce.
+1019,Příkaz 'break' nelze použít mimo smyčku.
+1020,Příkaz 'continue' nelze použít mimo smyčku.
+1023,Byla očekávána hexadecimální číslice.
+1024,Byl očekáván příkaz 'while'.
+1025,Návěstí bylo předefinováno.
+1026,Návěstí nebylo nalezeno.
+1027,Výraz 'default' lze v příkazu 'switch' použít jen jednou.
+1028,Byl očekáván identifikátor, řetězec nebo číslo.
+1029,Byl očekáván příkaz '@end'.
+1030,Podmíněná kompilace je vypnuta.
+1031,Byla očekávána konstanta.
+1032,Byl očekáván znak '@'
+1033,Bylo očekáváno klíčové slovo 'catch'.
+1034,Bylo očekáváno klíčové slovo 'var'.
+1035,Za příkazem throw musí na stejném zdrojovém řádku následovat výraz.
+1037,Příkazy with nejsou povoleny ve striktním režimu.
+1038,Duplicitní názvy formálních parametrů nejsou povoleny ve striktním režimu.
+1039,Osmičkové číselné literály a řídicí znaky nejsou povoleny ve striktním režimu.
+1041,Neplatné použití funkce eval ve striktním režimu.
+1042,Neplatné použití funkce arguments ve striktním režimu.
+1045,Volání funkce delete pro výraz není povoleno ve striktním režimu.
+1046,Několik definic vlastnosti není povoleno ve striktním režimu.
+1047,Ve striktním režimu nemohou být deklarace function vnořené v příkazu nebo v bloku. Mohou se vyskytovat pouze na nejvyšší úrovni nebo přímo v těle funkce.
+1048,Použití klíčového slova jako identifikátoru je neplatné.
+1049,Použití budoucího rezervovaného slova jako identifikátoru je neplatné.
+1050,Použití budoucího rezervovaného slova jako identifikátoru je neplatné. Název identifikátoru je ve striktním režimu rezervovaný.
+1051,Funkce Setter musí mít jeden argument.
+4096,JavaScript – chyba kompilace
+4097,JavaScript – chyba při běhu programu
+4098,Neznámá chyba při běhu programu
+5000,Nelze přiřadit objektu 'this'.
+5001,Bylo očekáváno číslo.
+5002,Byla očekávána funkce.
+5003,Nelze přiřadit výsledku funkce.
+5004,Objekt nelze indexovat.
+5005,Byl očekáván řetězec.
+5006,Byl očekáván objekt typu datum.
+5007,Byl očekáván objekt.
+5008,Nepovolená levá část přiřazení
+5009,Nedefinovaný identifikátor
+5010,Byla očekávána hodnota typu Boolean.
+5011,Nelze spustit kód z uvolněného skriptu.
+5012,Byl očekáván člen objektu.
+5013,Byl očekáván typ VBArray.
+5014,Byl očekáván objekt JavaScript.
+5015,Byl očekáván objekt typu Enumerator.
+5016,Byl očekáván objekt regulárního výrazu.
+5017,Chybná syntaxe regulárního výrazu
+5018,Neočekávaný kvantifikátor
+5019,Byl očekáván znak ']' v regulárním výrazu
+5020,Byl očekáván znak ')' v regulárním výrazu
+5021,Neplatný rozsah znakové sady
+5022,Výjimka byla generována, ale nebyla zachycena.
+5023,Funkce neobsahuje platný objekt prototypu.
+5024,Identifikátor URI, který má být kódován, obsahuje neplatný znak.
+5025,Identifikátor URI, který má být dekódován, nemá platné kódování.
+5026,Počet desetinných míst je mimo rozsah.
+5027,Přesnost je mimo rozsah.
+5028,Je očekáván objekt typu Array nebo Arguments.
+5029,Délka pole musí být konečné celé kladné číslo.
+5030,Délce pole musí být přiřazeno konečné celé kladné číslo.
+5031,Je očekáván objekt typu Array.
+5034,Cyklický odkaz v hodnotě argumentu není podporován
+5035,Neplatný argument pro nahrazení
+5038,Seznam argumentů je příliš rozsáhlý. Nelze ho proto použít.
+5039,Opětovná deklarace vlastnosti const
+5041,Členský objekt nelze konfigurovat.
+5042,Proměnná není definována ve striktním režimu.
+5043,Ve striktním režimu není povolen přístup k vlastnosti „caller“ funkce nebo objektu argumentů.
+5044,Ve striktním režimu není povolen přístup k vlastnosti „callee“ objektu argumentů.
+5045,Ve striktním režimu není povoleno přiřazení k vlastnostem jen pro čtení.
+5046,Nelze vytvořit vlastnost pro objekt, který nelze rozšířit.
+5047,Byl očekáván objekt.
+5048,Byl očekáván objekt.
+5049,Byl očekáván objekt.
+5050,Byl očekáván objekt.
+5051,Byla očekávána funkce.
+5052,Byla očekávána funkce.
+5053,Vlastnost nemůže mít přístupový objekt i hodnotu.
+5054,Metoda 'this' vrací hodnotu Null nebo není definována.
+5055,Byl očekáván objekt.
+5056,Byla očekávána funkce.
+5057,Byl očekáván řetězec.
+5058,Byla očekávána hodnota typu Boolean.
+5059,Bylo očekáváno datum.
+5060,Bylo očekáváno číslo.
+5061,Byl očekáván typ VBArray.
+5062,Byl očekáván objekt JavaScript.
+5063,Byl očekáván objekt typu Enumerator.
+5064,Byl očekáván objekt RegExp.
+5065,Neplatný argument funkce
+5066,Byl očekáván objekt.
+5067,Byl očekáván objekt JavaScript.
+5068,Byla očekávána funkce.
+5069,Byl očekáván typ VBArray.
+5070,Byl očekáván objekt.
+5071,Byl očekáván objekt.
+5072,Neplatná vlastnost length
+5073,Je očekáván objekt typu Array nebo Arguments.
+5074,Neplatný operand
+5075,Neplatný operand
+5076,Neplatný popisovač vlastnosti
+5077,Vlastnost nelze definovat: Objekt nelze rozšířit.
+5078,Nekonfigurovatelnou vlastnost nelze předefinovat.
+5079,Nezapisovatelnou vlastnost nelze změnit.
+5080,Vlastnost nelze změnit: Vlastnost length není zapisovatelná.
+5081,Vlastnost nelze definovat.
+5082,Argument konstruktoru typovaného pole je neplatný.
+5083,„this“ není objekt typovaného pole.
+5084,Neplatný posun/délka při vytváření typovaného pole
+5085,Neplatná počáteční/koncová hodnota v metodě dílčího pole typovaného pole
+5086,Neplatný zdroj v sadě typovaného pole
+5087,„this“ není objekt DataView.
+5088,Neplatné argumenty v objektu DataView
+5089,Přístup operace DataView je mimo zadanou délku vyrovnávací paměti.
+5090,Neplatné argumenty v objektu DataView
+5091,neplatný podpis funkce
+5092,neplatný podpis vlastnosti
+5093,neplatný typ vstupního parametru
+5094,neplatný výstupní parametr
+5095,Ve striktním režimu není povolen přístup k vlastnosti „arguments“ funkce.
+5096,Je očekáván zkontrolovatelný objekt.
+5097,Argument nelze převést na typ char.
+5098,Argument nelze převést na typ GUID.
+5099,Bylo očekáváno rozhraní IInspectable.
+5100,Objekt nelze převést na strukturu: U objektu chybí očekávaná vlastnost.
+5101,Neznámý typ
+5102,Byla volána funkce s příliš málo argumenty.
+5103,Typ není konstruovatelný.
+5104,Hodnotu nelze převést na objekt PropertyValue: Typ není objektem PropertyValue podporován.
+5105,Hodnotu nelze převést na objekt IInspectable: Typ není objektem IInspectable podporován.
+5106,Datum nelze převést na objekt Windows.Foundation.DateTime: Hodnota je mimo platný rozsah.
+5107,Hodnotu nelze převést na objekt Windows.Foundation.TimeSpan: Hodnota je mimo platný rozsah.
+5108,Neplatný přístup k již uvolněnému zkontrolovatelnému objektu
+5109,Nelze uvolnit již uvolněný zkontrolovatelný objekt.
+5110,Objekt this nemá očekávaný typ.
+5111,Je určena neplatná délka a velikost pole.
+5112,Při pokusu o získání informací o metadatech došlo k neočekávané chybě.
+5200,Stav je chybový, ale metoda getResults nevrátila chybu.
+5201,Parametr status předaný do dokončené obslužné rutiny chybí nebo je neplatný.
+5202,Parametr sender předaný do dokončené obslužné rutiny chybí nebo je neplatný.
+6000,Nekonečno
+6001,- Nekonečno
+10438,Objekt neumožňuje použití vlastnosti či metody %s.
+10449,Argument funkce %s není volitelný.
+15001,%s není číslo.
+15002,%s není funkce.
+15004,%s není indexovatelný objekt.
+15005,%s není řetězec.
+15006,%s není objekt typu Date.
+15007,%s má hodnotu null nebo není objekt.
+15008,Nelze přiřadit k objektu %s.
+15009,Objekt %s není definován.
+15010,%s není typu Boolean.
+15012,Objekt %s nelze odstranit.
+15013,%s není typu VBArray.
+15014,%s není objekt JavaScript.
+15015,%s není objekt typu Enumerator.
+15016,%s není objekt regulárního výrazu.
+15028,%s není objekt typu Array nebo Arguments.
+15031,%s není objekt typu Array.
+15036,Atribut %s ve třídě property descriptor nemůže být u tohoto objektu nastaven na hodnotu true.
+15037,Atribut %s ve třídě property descriptor nemůže být u tohoto objektu nastaven na hodnotu false.
+15039,Opětovná deklarace konstanty %s
+15041,Ve striktním režimu není povoleno volání metody delete pro objekt %s.
+15047,Nelze nastavit vlastnost %s nedefinovaného nebo nulového odkazu.
+15048,Nelze načíst vlastnost %s nedefinovaného nebo nulového odkazu.
+15049,Nelze odstranit vlastnost %s nedefinovaného nebo nulového odkazu.
+15050,Nelze přistupovat k vlastnosti %s: Typ VarDate nepodporuje uživatelské vlastnosti.
+15051,Hodnota vlastnosti %s není objektem Function.
+15052,Vlastnost %s má hodnotu Null nebo není definována. Vlastnost není objektem Function.
+15054,%s: Metoda 'this' má hodnotu Null nebo není definována.
+15055,%s: Metoda 'this' není objektem.
+15056,%s: Metoda 'this' není objektem Function.
+15057,%s: Metoda 'this' není řetězcovým objektem (typ String).
+15058,%s: Metoda 'this' není booleovským objektem (typ Boolean).
+15059,%s: Metoda 'this' není kalendářním objektem (typ Date).
+15060,%s: Metoda 'this' není číselným objektem (typ Number).
+15061,%s: Metoda 'this' není objektem VBArray.
+15062,%s: Metoda 'this' není objektem jazyka JavaScript.
+15063,%s: Metoda 'this' není objektem Enumerator.
+15064,%s: Metoda 'this' není objektem RegExp.
+15065,%s: Neplatný argument
+15066,%s: Argument není objektem.
+15067,%s: Argument není objektem jazyka JavaScript.
+15068,%s: Argument není objektem Function.
+15069,%s: Argument není objektem VBArray.
+15070,%s: Argument má hodnotu Null nebo není definován.
+15071,%s: Argument není objektem a nemá hodnotu Null.
+15072,%s: Argument nemá platnou hodnotu vlastnosti length.
+15073,%s: Byl očekáván objekt typu Array nebo Arguments.
+15074,Neplatný operand %s: Byl očekáván objekt.
+15075,Neplatný operand %s: Byla očekávána funkce.
+15076,Neplatný popisovač pro vlastnost %s
+15077,Vlastnost %s nelze definovat: Objekt nelze rozšířit.
+15078,Nekonfigurovatelnou vlastnost %s nelze předefinovat.
+15079,Nezapisovatelnou vlastnost %s nelze změnit.
+15080,Vlastnost %s nelze změnit: Vlastnost length není zapisovatelná.
+15081,Vlastnost %s nelze definovat.
+15088,V metodě DataView není zadán požadovaný argument %s.
+15090,Argument %s konstruktoru objektu DataView je neplatný.
+15091,Funkce %s má neplatný podpis a nelze ji volat.
+15092,Vlastnost %s má neplatný podpis a nelze k ní získat přístup.
+15093,Třída modulu runtime %s, která má jako výchozí rozhraní Windows.Foundation.IPropertyValue, není jako typ vstupního parametru podporována.
+15094,Objekt s rozhraním Windows.Foundation.IPropertyValue, který má název třídy v modulu runtime %s, není podporován jako výstupní parametr.
+15096,%s: „this“ není prozkoumatelný objekt.
+15097,%s: Argument nelze převést na typ char.
+15098,%s: Argument nelze převést na typ GUID.
+15099,%s: Vrácenou hodnotu nelze převést na IInspectable.
+15100,Objekt nelze převést na strukturu: U objektu chybí očekávaná vlastnost %s.
+15101,Typ %s nebyl nalezen.
+15102,%s: Byla volána funkce s příliš málo argumenty.
+15103,%s: Typ není konstruovatelný.
+15104,Hodnotu nelze převést na objekt PropertyValue: Objekt PropertyValue nepodporuje %s.
+15105,Hodnotu nelze převést na IInspectable: Rozhraní IInspectable nepodporuje %s.
+15108,%s: Zkontrolovatelný objekt „this“ je uvolněn a nelze k němu získat přístup.
+15110,Objekt this nemá očekávaný typ: %s
+15112,%s: Při pokusu o získání informací o metadatech došlo k neočekávané chybě.
+32812,Zadané datum není v kalendáři národního prostředí k dispozici.
diff --git a/src/sentry/data/error-locale/da-DK.txt b/src/sentry/data/error-locale/da-DK.txt
new file mode 100644
index 00000000000000..f486a3ebba2c3a
--- /dev/null
+++ b/src/sentry/data/error-locale/da-DK.txt
@@ -0,0 +1,289 @@
+5,Procedurekaldet eller argumentet er ugyldigt
+6,Overløb
+7,Der er ikke mere hukommelse
+9,Indekset er uden for området
+10,Denne matrix er fast eller midlertidigt låst
+11,Division med nul
+13,Typer stemmer ikke overens
+14,Der er ikke mere strengplads
+17,Den ønskede handling kan ikke udføres
+28,Der er ikke mere stakplads
+35,Sub- eller Function-procedure er ikke defineret
+48,Der opstod en fejl under indlæsning af DLL-filen
+51,Der opstod en intern fejl
+52,Forkert filnavn eller -nummer
+53,Filen blev ikke fundet
+54,Forkert filtilstand
+55,Filen er allerede åben
+57,I/O-fejl i forbindelse med enhed
+58,Filen findes allerede
+61,Disken er fuld
+62,Input efter filens slutning
+67,Der er for mange filer
+68,Enheden er ikke tilgængelig
+70,Tilladelse nægtet
+71,Disken er ikke klar
+74,Der kan ikke omdøbes til et navn på et andet drev
+75,Der opstod en fejl ved adgang til sti eller fil
+76,Stien blev ikke fundet
+91,Objektvariablen eller With-blokken er ikke angivet
+92,For-løkken er ikke initialiseret
+94,Null er brugt ugyldigt
+322,Den nødvendige midlertidige fil kan ikke oprettes
+424,Et objekt er obligatorisk
+429,Automation-serveren kan ikke oprette objektet
+430,Klassen understøtter ikke Automation
+432,Fil- eller klassenavnet blev ikke fundet under Automation-handlingen
+438,Objektet understøtter ikke denne egenskab eller metode
+440,Der opstod en Automation-fejl
+445,Objektet understøtter ikke denne handling
+446,Objektet understøtter ikke navngivne argumenter
+447,Objektet understøtter ikke den nuværende indstilling for landekode
+448,Det navngivne argument blev ikke fundet
+449,Argumentet er ikke valgfrit
+450,Antallet af argumenter er forkert, eller egenskabstildelingen er ugyldig
+451,Objektet er ikke en samling
+453,Den angivne DLL-funktion blev ikke fundet
+458,Variablen bruger en Automation-type, der ikke understøttes i JavaScript
+462,Fjernserveren eksisterer ikke eller er ikke tilgængelig
+501,Der kan ikke tildeles til variabel
+502,Objektet er ikke klar til scripting
+503,Objektet er ikke klar til initialisering
+504,Objektet er ikke klar til oprettelse
+507,Der er opstået en undtagelse
+1001,Der er ikke mere hukommelse
+1002,Der er en syntaksfejl
+1003,Tegnet ':' var ventet
+1004,Tegnet ';' var ventet
+1005,Tegnet '(' var ventet
+1006,Tegnet ')' var ventet
+1007,Tegnet ']' var ventet
+1008,Tegnet '{' var ventet
+1009,Tegnet '}' var ventet
+1010,Id var ventet
+1011,Tegnet '=' var ventet
+1012,Tegnet '/' var ventet
+1013,Tallet er ugyldigt
+1014,Tegnet er ugyldigt
+1015,Strengkonstanten er uafsluttet
+1016,Bemærkningen er uafsluttet
+1018,'return'-sætningen uden for funktionen
+1019,'break' kan ikke optræde uden for løkken
+1020,'continue' kan ikke optræde uden for løkken
+1023,Et hexadecimalt ciffer var ventet
+1024,'while' var ventet
+1025,Linjemærket blev defineret igen
+1026,Linjemærket blev ikke fundet
+1027,'default' kan kun optræde én gang i en 'switch'-sætning
+1028,Et id, en streng eller et tal var ventet
+1029,'@end' var ventet
+1030,Betinget kompilering er slået fra
+1031,En konstant var ventet
+1032,'@' var ventet
+1033,Forventede 'catch'
+1034,Forventede 'var'
+1035,'throw' skal følges af et udtryk i samme kildelinje
+1037,'with'-sætninger er ikke tilladt i bundet tilstand
+1038,Identiske formelle parameternavne er ikke tilladt i bundet tilstand
+1039,Oktale numeriske konstanter og escape-tegn er ikke tilladt i bundet tilstand
+1041,'eval' bruges ugyldigt i bundet tilstand
+1042,'arguments' bruges ugyldigt i bundet tilstand
+1045,Kald af delete på udtryk er ikke tilladt i bundet tilstand
+1046,Flere definitioner af en egenskab er ikke tilladt i bundet tilstand
+1047,I bundet tilstand kan funktionserklæringer ikke være indlejret i en sætning eller en blok. De kan kun forekomme på øverste niveau eller direkte i funktionsteksten.
+1048,Brugen af et nøgleord som identifikator er ugyldig
+1049,Brugen af et fremtidigt reserveret ord for en identifikator er ugyldig
+1050,Brugen af et fremtidigt reserveret ord for en identifikator er ugyldig. Identifikatornavnet reserveres i bundet tilstand.
+1051,Setter-funktioner skal have et argument
+4096,Der opstod en JavaScript-kompileringsfejl
+4097,Der opstod en JavaScript-kørselsfejl
+4098,Der opstod en ukendt kørselsfejl
+5000,Der kan ikke tildeles til 'this'
+5001,Et tal var ventet
+5002,En funktion var ventet
+5003,Der kan ikke tildeles til resultatet af en funktion
+5004,Objektet kan ikke indekseres
+5005,En streng var ventet
+5006,Et datoobjekt var ventet
+5007,Et objekt var ventet
+5008,Venstresiden er ugyldig i tildelingen
+5009,Id er ikke defineret
+5010,En boolesk værdi var ventet
+5011,Programkoden kan ikke køres fra et frigjort script
+5012,Et objektmedlem var ventet
+5013,VBArray var ventet
+5014,Et JavaScript-objekt var ventet
+5015,Et enumeratorobjekt var ventet
+5016,Et regulært udtryksobjekt var ventet
+5017,Der er en syntaksfejl i det regulære udtryk
+5018,Uventet kvantor
+5019,Tegnet ']' var ventet i det regulære udtryk
+5020,Tegnet ')' var ventet i det regulære udtryk
+5021,Ugyldigt område i tegnsættet
+5022,Undtagelse opstået og ikke fanget
+5023,Funktionen har ikke et gyldigt prototypeobjekt
+5024,Den URI, der skal kodes, indeholder et ugyldigt tegn
+5025,Den URI, der skal dekodes, er ikke kodet korrekt
+5026,Antallet af brøkdele er uden for området
+5027,Nøjagtigheden er uden for området
+5028,Der blev forventet et objekt af typen Array eller Arguments
+5029,Matrixlængden skal være en endelig positiv værdi
+5030,Matrixlængden skal tildeles en endelig positiv værdi
+5031,Der blev forventet et objekt af typen Array
+5034,Den cirkulære reference i værdiargumentet understøttes ikke
+5035,Erstatningsargumentet er ugyldigt
+5038,Argumentlisten er for stor til at blive anvendt
+5039,Generklæring af egenskaben const
+5041,Objektmedlemmet kan ikke konfigureres
+5042,Variablen er ikke defineret i bundet tilstand
+5043,Der er ikke adgang til 'caller'-egenskaben for et funktions- eller argumentobjekt i bundet tilstand
+5044,Der er ikke adgang til 'callee'-egenskaben for et argumentobjekt i bundet tilstand
+5045,Det er ikke tilladt at tildele til skrivebeskyttede egenskaber i bundet tilstand
+5046,Der kan ikke oprettes en egenskab for et objekt, der ikke kan udvides
+5047,Et objekt var ventet
+5048,Et objekt var ventet
+5049,Et objekt var ventet
+5050,Et objekt var ventet
+5051,En funktion var ventet
+5052,En funktion var ventet
+5053,Egenskaben kan ikke have både accessorer og en værdi
+5054,'this' er null eller er ikke defineret
+5055,Et objekt var ventet
+5056,En funktion var ventet
+5057,En streng var ventet
+5058,En boolesk værdi var ventet
+5059,En dato var ventet
+5060,Et tal var ventet
+5061,VBArray var ventet
+5062,Et JavaScript-objekt var ventet
+5063,Et enumeratorobjekt var ventet
+5064,Et RegExp-objekt var ventet
+5065,Funktionsargumentet var ugyldigt
+5066,Et objekt var ventet
+5067,Et JavaScript-objekt var ventet
+5068,En funktion var ventet
+5069,VBArray var ventet
+5070,Et objekt var ventet
+5071,Et objekt var ventet
+5072,Egenskaben 'length' er ugyldig
+5073,Der blev forventet et objekt af typen Array eller Arguments
+5074,Operanden er ugyldig
+5075,Operanden er ugyldig
+5076,Egenskabsbeskrivelsen er ugyldig
+5077,Egenskaben kan ikke defineres: objektet kan ikke udvides
+5078,En egenskab, der ikke kan konfigureres, kan ikke omdefineres
+5079,En egenskab, der er skrivebeskyttet, kan ikke ændres
+5080,Egenskaben kan ikke ændres: 'length' kan ikke ændres
+5081,Egenskaben kan ikke defineres
+5082,Konstruktørargument for typebestem array er ugyldigt
+5083,'this' er ikke et typebestemt arrayobjekt
+5084,Ugyldig forskydning/længde ved oprettelse af typebestemt array
+5085,Ugyldig værdi begin/end i under-arraymetode for typebestemt array
+5086,Ugyldig kilde i typebestemt array-sæt
+5087,'this' er ikke et DataView-objekt
+5088,Ugyldige argumenter i DataView
+5089,Adgang til DataView-handling over angivet bufferlængde
+5090,Ugyldige argumenter i DataView
+5091,ugyldig funktion signatur
+5092,ugyldige egenskab signatur
+5093,ugyldig inputparametertype
+5094,ugyldig outputparameter
+5095,Adgang til egenskaben 'arguments' for en funktion er ikke tilladt i bundet tilstand
+5096,Et kontrollérbart objekt var ventet
+5097,Argumentet kunne ikke konverteres til typen 'char'
+5098,Argumentet kunne ikke konverteres til typen 'GUID'
+5099,IInspectable var ventet
+5100,Objektet kunne ikke konverteres til struktur: Objektet mangler forventet egenskab
+5101,Ukendt type
+5102,Funktionen blev kaldt med for få argumenter
+5103,Typen kan ikke konstrueres
+5104,Værdien kunne ikke konverteres til PropertyValue: Typen understøttes ikke af PropertyValue
+5105,Værdien kunne ikke konverteres til IInspectable: Typen understøttes ikke af IInspectable
+5106,Datoen kunne ikke konverteres til Windows.Foundation.DateTime: Værdien er uden for det gyldige interval
+5107,Værdien kunne ikke konverteres til Windows.Foundation.TimeSpan: Værdien er uden for det gyldige interval
+5108,Adgang til allerede frigivet kontrollérbart objekt er ugyldig
+5109,Allerede frigivet kontrollérbart objekt kan ikke frigives
+5110,'dette' er ikke af den forventede type
+5111,Der er angivet ugyldig længde og størrelse for matrixen
+5112,Der opstod en uventet fejl under forsøg på at hente metadataoplysninger
+5200,Status er 'fejl', men getResults returnerede ingen fejl
+5201,Manglende eller ugyldig statusparameter overført til afsluttet handler
+5202,Manglende eller ugyldig afsenderparameter overført til afsluttet handler
+6000,Uendeligt tal
+6001,Negativt uendeligt tal
+10438,Objektet understøtter ikke egenskaben eller metoden '%s'
+10449,Argumentet til funktionen '%s' skal angives
+15001,'%s' er ikke et tal
+15002,'%s' er ikke en funktion
+15004,'%s' er et objekt, der ikke kan indekseres
+15005,'%s' er ikke en streng
+15006,'%s' er ikke et datoobjekt
+15007,'%s' er null eller ikke et objekt
+15008,Der kan ikke tildeles til '%s'
+15009,'%s' er ikke defineret
+15010,'%s' er ikke en boolesk værdi
+15012,'%s' kan ikke slettes
+15013,'%s' er ikke et VBArray
+15014,'%s' er ikke et JavaScript-objekt
+15015,'%s' er ikke et optællerobjekt
+15016,'%s' er ikke et regulært udtryksobjekt
+15028,%s er ikke et objekt af typen Array eller Arguments
+15031,%s er ikke et objekt af typen Array
+15036,'%s'-attributten på egenskabsbeskrivelsen kan ikke sættes til 'true' på dette objekt
+15037,'%s'-attributten på egenskabsbeskrivelsen kan ikke sættes til 'false' på dette objekt
+15039,Generklæring af const '%s'
+15041,Kald af delete på '%s' er ikke tilladt i bundet tilstand
+15047,Egenskaben '%s' kan ikke angives for reference, der er udefineret eller null
+15048,Egenskaben '%s' kan ikke hentes for reference, der er udefineret eller null
+15049,Egenskaben '%s' kan ikke slettes reference, der er udefineret eller null
+15050,Der kan ikke opnås adgang til egenskaben '%s': Typen 'VarDate' understøtter ikke brugerdefinerede egenskaber
+15051,Værdien af egenskaben '%s' er ikke et Function-objekt
+15052,Værdien af egenskaben '%s' er null eller er ikke defineret. Det er ikke et Function-objekt
+15054,%s: 'this' er null eller er ikke defineret
+15055,%s: 'this' er ikke et objekt
+15056,%s: 'this' er ikke et Function-objekt
+15057,%s: 'this' er ikke et String-objekt
+15058,%s: 'this' er ikke et Boolean-objekt
+15059,%s: 'this' er ikke et Date-objekt
+15060,%s: 'this' er ikke et Number-objekt
+15061,%s: 'this' er ikke et VBArray-objekt
+15062,%s: 'this' er ikke et JavaScript-objekt
+15063,%s: 'this' er ikke et Enumerator-objekt
+15064,%s: 'this' er ikke et RegExp-objekt
+15065,%s: Argumentet er ugyldigt
+15066,%s: Argumentet er ikke et objekt
+15067,%s: Argumentet er ikke et JavaScript-objekt
+15068,%s: Argumentet er ikke et Function-objekt
+15069,%s: Argumentet er ikke et VBArray-objekt
+15070,%s: Argumentet er null eller er ikke defineret
+15071,%s: Argumentet er ikke et objekt og er ikke null
+15072,%s: Argumentet har ikke en gyldig 'length'-egenskab
+15073,%s: Matrix eller argumentobjekt var ventet
+15074,Operanden til '%s' er ugyldig: Et objekt var ventet
+15075,Operanden til '%s' er ugyldig: En funktion var ventet
+15076,Beskrivelsen for egenskaben '%s' er ugyldig
+15077,Egenskaben '%s' kan ikke defineres: objektet kan ikke udvides
+15078,Egenskaben '%s' kan ikke omdefineres, da den ikke kan konfigureres
+15079,Egenskaben '%s' kan ikke ændres, da den er skrivebeskyttet
+15080,Egenskaben '%s' kan ikke ændres: 'length' kan ikke ændres
+15081,Egenskaben '%s' kan ikke defineres
+15088,Påkrævet argument i %s i DataView-metode er ikke angivet
+15090,DataView-konstruktørargumentet %s er ugyldigt
+15091,Funktionen '%s' har en ugyldig signatur og kan ikke kaldes
+15092,Egenskaben '%s' har en ugyldig signatur og kan ikke åbnes
+15093,Kørselsklassen %s, der har Windows.Foundation.IPropertyValue, som standardgrænseflade, understøttes ikke som inputparametertype
+15094,Objektet med grænsefladen Windows.Foundation.IPropertyValue, der har navnet for kørselsklasse %s, understøttes ikke som out-parameter
+15096,%s: 'this' er ikke et kontrollérbart objekt
+15097,%s: Argumentet kunne ikke konverteres til typen 'char'
+15098,%s: Argumentet kunne ikke konverteres til typen 'GUID'
+15099,%s: Returværdien kunne ikke konverteres til IInspectable
+15100,Objektet kunne ikke konverteres til struktur: Objektet mangler forventet egenskab '%s'
+15101,Typen '%s' blev ikke fundet
+15102,%s: Funktionen blev kaldt med for få argumenter
+15103,%s: Typen kan ikke konstrueres
+15104,Værdien kunne ikke konverteres til PropertyValue: %s understøttes ikke af PropertyValue
+15105,Værdien kunne ikke konverteres til IInspectable: %s understøttes ikke af IInspectable
+15108,%s: Det kontrollérbare objekt 'this' er frigivet, og der er ingen adgang til det
+15110,'dette' er ikke af den forventede type: %s
+15112,%s: Der opstod en uventet fejl under forsøg på at hente metadataoplysninger
+32812,Den angivne dato findes ikke i kalenderen for den nuværende landekode
diff --git a/src/sentry/data/error-locale/de-DE.txt b/src/sentry/data/error-locale/de-DE.txt
new file mode 100644
index 00000000000000..c42ae3dec322c8
--- /dev/null
+++ b/src/sentry/data/error-locale/de-DE.txt
@@ -0,0 +1,289 @@
+5,Ungültiger Prozeduraufruf oder ungültiges Argument
+6,Überlauf
+7,Nicht genügend Arbeitsspeicher.
+9,Index außerhalb des gültigen Bereichs
+10,Array ist unveränderlich oder momentan gesperrt.
+11,Division durch Null
+13,Typenkonflikt
+14,Nicht genügend Zeichenfolgenspeicher
+17,Angeforderte Operation kann nicht durchgeführt werden.
+28,Nicht genügend Stackspeicher.
+35,Sub- oder Function-Prozedur nicht definiert
+48,Fehler beim Laden einer DLL
+51,Interner Fehler
+52,Ungültige(r) Dateiname oder -nummer.
+53,Die Datei wurde nicht gefunden.
+54,Ungültiger Dateimodus.
+55,Die Datei ist bereits geöffnet.
+57,Geräte-E/A-Fehler
+58,Die Datei ist bereits vorhanden.
+61,Der Datenträger ist voll.
+62,Eingabe hinter Dateiende.
+67,Zu viele Dateien.
+68,Das Gerät ist nicht verfügbar.
+70,Erlaubnis verweigert.
+71,Der Datenträger ist nicht bereit.
+74,Umbenennen bei Angabe unterschiedlicher Laufwerke nicht möglich.
+75,Fehler beim Zugriff auf Pfad/Datei.
+76,Der Pfad wurde nicht gefunden.
+91,Objektvariable oder Variable für With-Block nicht gesetzt
+92,For-Schleife nicht initialisiert
+94,Ungültige Verwendung von Null.
+322,Die erforderliche temporäre Datei kann nicht erstellt werden.
+424,Objekt erforderlich
+429,Automatisierungsserver kann Objekt nicht erstellen.
+430,Klasse unterstützt keine Automatisierung.
+432,Datei- oder Klassenname während der Automatisierungsoperation nicht gefunden.
+438,Das Objekt unterstützt diese Eigenschaft oder Methode nicht.
+440,Automatisierungsfehler
+445,Das Objekt unterstützt diese Aktion nicht.
+446,Das Objekt unterstützt benannte Argumente nicht.
+447,Das Objekt unterstützt die aktuelle Ländereinstellung nicht.
+448,Benanntes Argument nicht gefunden
+449,Das Argument ist nicht optional.
+450,Falsche Anzahl an Argumenten oder ungültige Eigenschaftszuweisung
+451,Das Objekt ist keine Auflistung.
+453,Angegebene DLL-Funktion nicht gefunden
+458,Variable verwendet einen Automatisierungstyp, der in JavaScript nicht unterstützt wird.
+462,Der Remoteservercomputer ist existiert nicht oder ist nicht verfügbar
+501,Zuweisung an Variable nicht möglich
+502,Das Objekt ist nicht sicher für Skriptingverarbeitung.
+503,Das Objekt ist nicht sicher für Initialisierung.
+504,Das Objekt ist nicht sicher für Erstellung.
+507,Eine Ausnahmebedingung ist aufgetreten.
+1001,Nicht genügend Arbeitsspeicher.
+1002,Syntaxfehler
+1003,':' erwartet
+1004,';' erwartet
+1005,'(' erwartet
+1006,')' erwartet
+1007,']' erwartet
+1008,'{' erwartet
+1009,'}' erwartet
+1010,Bezeichner erwartet
+1011,'=' erwartet
+1012,'/' erwartet
+1013,Ungültige Zahl
+1014,Ungültiges Zeichen
+1015,Nicht abgeschlossene Zeichenfolgenkonstante
+1016,Nicht abgeschlossener Kommentar
+1018,'return'-Anweisung außerhalb einer Funktion
+1019,'break' außerhalb einer Schleife nicht möglich
+1020,'continue' außerhalb einer Schleife nicht möglich
+1023,Hexadezimalziffer erwartet
+1024,'while' erwartet
+1025,Marke neu definiert
+1026,Marke nicht gefunden
+1027,'default' kann nur einmal in einer 'switch'-Anweisung auftreten
+1028,Bezeichner, Zeichenfolge oder Zahl erwartet
+1029,'@end' erwartet
+1030,Bedingte Kompilierung ist ausgeschaltet
+1031,Konstante erwartet
+1032,'@' erwartet
+1033,'catch' erwartet
+1034,'var' erwartet
+1035,Auf "throw" muss ein Ausdruck in derselben Quellzeile folgen
+1037,"with"-Anweisungen sind im Strict-Modus nicht zulässig
+1038,Doppelte formale Parameternamen sind im Strict-Modus nicht zulässig.
+1039,Oktale numerische Literale und Escapezeichen sind im Strict-Modus nicht zulässig.
+1041,Ungültige Verwendung von "eval" im Strict-Modus.
+1042,Ungültige Verwendung von "arguments" im Strict-Modus.
+1045,Das Aufrufen des Löschvorgangs nach einem Ausdruck ist im Strict-Modus nicht zulässig.
+1046,Mehrere Definitionen einer Eigenschaft sind im Strict-Modus nicht zulässig.
+1047,Im Strict-Modus können keine Funktionsdeklarationen in einer Anweisung oder einem Block geschachtelt werden. Sie werden möglicherweise nur auf der obersten Ebene oder direkt in einem Funktionstext angezeigt.
+1048,Die Verwendung eines Schlüsselworts für eine ID ist ungültig.
+1049,Die Verwendung eines zukünftigen reservierten Worts für eine ID ist ungültig.
+1050,Die Verwendung eines zukünftigen reservierten Worts für eine ID ist ungültig. Der ID-Name ist im Strict-Modus reserviert.
+1051,Setter-Funktionen müssen ein Argument besitzen.
+4096,Kompilierungsfehler in JavaScript
+4097,Laufzeitfehler in JavaScript
+4098,Unbekannter Laufzeitfehler
+5000,Zuweisung an 'this' nicht möglich
+5001,Zahl erwartet
+5002,Funktion erwartet
+5003,Kann dem Ergebnis einer Funktion nicht zugewiesen werden
+5004,Objekt kann nicht indiziert werden
+5005,Zeichenfolge erwartet
+5006,Datumsobjekt erwartet
+5007,Objekt erwartet
+5008,Ungültige linke Seite in Zuweisung
+5009,Nicht definierter Bezeichner
+5010,Boolesch erwartet
+5011,Code eines freigegebenen Skripts kann nicht ausgeführt werden
+5012,Objektmitglied erwartet
+5013,VBArray erwartet
+5014,JavaScript-Objekt erwartet
+5015,Aufzählungsobjekt erwartet
+5016,Regulärer Ausdruck als Objekt erwartet
+5017,Syntaxfehler in regulärem Ausdruck
+5018,Unerwarteter Quantifizierer
+5019,Erwartet ']' in regulärem Ausdruck
+5020,Erwartet ')' in regulärem Ausdruck
+5021,Ungültiger Bereich in Zeichensatz
+5022,Annahme ausgelöst und nicht aufgefangen.
+5023,Die Funktion hat kein gültiges Prototyp-Objekt.
+5024,Der zu verschlüsselnde URI enthält ein ungültiges Zeichen.
+5025,Der zu entschlüsselnde URI hat keine gültige Verschlüsselung.
+5026,Die Anzahl der Bruchstellen befindet sich außerhalb des gültigen Bereichs.
+5027,Die Präzision befindet sich außerhalb des gültigen Bereichs.
+5028,Array- oder Arguments-Objekt erwartet
+5029,Die Arraylänge muss eine finite positive Ganzzahl sein.
+5030,Der Arraylänge muss eine finite positive Zahl zugewiesen sein.
+5031,Arrayobjekt erwartet
+5034,Zirkelverweis wird im Wertargument nicht unterstützt.
+5035,Ungültiges Ersetzungsargument.
+5038,Die Argumentliste ist zu lang, um angewendet zu werden.
+5039,Neudeklaration der "const"-Eigenschaft.
+5041,Nicht konfigurierbares Objektmitglied.
+5042,Nicht definierte Variable im Strict-Modus.
+5043,Der Zugriff auf die caller-Eigenschaft einer Funktion oder eines arguments-Objekts ist im Strict-Modus nicht zulässig.
+5044,Der Zugriff auf die callee-Eigenschaft eines arguments-Objekts ist im Strict-Modus nicht zulässig.
+5045,Die Zuordnung zu schreibgeschützten Eigenschaften ist im Strict-Modus nicht zulässig.
+5046,Die Eigenschaft für ein nicht erweiterbares Objekt kann nicht erstellt werden.
+5047,Objekt erwartet
+5048,Objekt erwartet
+5049,Objekt erwartet
+5050,Objekt erwartet
+5051,Funktion erwartet
+5052,Funktion erwartet
+5053,Die Eigenschaft kann nicht sowohl Accessors als auch einen Wert aufweisen.
+5054,'this' ist Null oder undefiniert.
+5055,Objekt erwartet
+5056,Funktion erwartet
+5057,Zeichenfolge erwartet
+5058,Boolesch erwartet
+5059,Datum erwartet
+5060,Zahl erwartet
+5061,VBArray erwartet
+5062,JavaScript-Objekt erwartet
+5063,Aufzählungsobjekt erwartet
+5064,RegExp-Objekt erwartet
+5065,Ungültiges Funktionsargument
+5066,Objekt erwartet
+5067,JavaScript-Objekt erwartet
+5068,Funktion erwartet
+5069,VBArray erwartet
+5070,Objekt erwartet
+5071,Objekt erwartet
+5072,Ungültige "length"-Eigenschaft
+5073,Array- oder Arguments-Objekt erwartet
+5074,Ungültiger Operand
+5075,Ungültiger Operand
+5076,Ungültiger Eigenschaftendeskriptor
+5077,Die Eigenschaft kann nicht definiert werden: Das Objekt ist nicht erweiterbar
+5078,Die nicht konfigurierbare Eigenschaft kann nicht neu definiert werden
+5079,Die schreibgeschützte Eigenschaft kann nicht geändert werden
+5080,Die Eigenschaft kann nicht geändert werden: "length" ist schreibgeschützt
+5081,Die Eigenschaft kann nicht definiert werden
+5082,Ungültiges Konstruktorargument in typisiertem Array.
+5083,'this' ist kein Objekt für ein typisiertes Array.
+5084,Ungültiger Offset oder ungültige Länge beim Erstellen eines typisierten Arrays.
+5085,Ungültiger Start- oder Endwert in Unterarraymethode eines typisierten Arrays.
+5086,Ungültige Quelle in typisiertem Array.
+5087,'this' ist kein Datenansichtsobjekt.
+5088,Ungültige Argumente in Datenansicht.
+5089,Zugriff eines Datenansichtsvorgangs jenseits der angegebenen Pufferlänge.
+5090,Ungültige Argumente in Datenansicht.
+5091,Ungültige Funktionssignatur.
+5092,Ungültige Eigenschaftssignatur.
+5093,Ungültiger Eingabeparametertyp.
+5094,Ungültiger Eingabeparameter
+5095,Der Zugriff auf die arguments-Eigenschaft einer Funktion ist im Strict-Modus nicht zulässig.
+5096,Inspectable-Objekt erwartet
+5097,Fehler beim Konvertieren des Arguments in Typ "char"
+5098,Fehler beim Konvertieren des Arguments in Typ "GUID"
+5099,"IInspectable" erwartet
+5100,Fehler beim Konvertieren des Objekts in Struktur: Objekt fehlt erwartete Eigenschaft.
+5101,Unbekannter Typ
+5102,Funktion aufgerufen mit zu wenig Argumenten
+5103,Typ kann nicht konstruiert werden.
+5104,Fehler beim Konvertieren des Werts in "PropertyValue": Typ nicht unterstützt von "PropertyValue"
+5105,Fehler beim Konvertieren des Werts zu "IInspectable": Typ nicht unterstützt von "IInspectable"
+5106,Fehler beim Konvertieren des Datums zu "Windows.Foundation.DateTime": Wert außerhalb des gültigen Bereichs
+5107,Fehler beim Konvertieren des Werts in "Windows.Foundation.TimeSpan": Wert außerhalb des gültigen Bereichs
+5108,Ungültiger Zugriff auf bereits freigegebenes Inspectable-Objekt
+5109,Bereits freigegebenes Inspectable-Objekt kann nicht freigegeben werden.
+5110,"this" ist nicht vom erwarteten Typ.
+5111,Für das Array wurde eine nicht zulässige Länge und Größe angegeben.
+5112,Unerwarteter Fehler beim Abrufen von Metadateninformationen.
+5200,Der Status lautet "error", es wurde jedoch kein Fehler von "getResults" zurückgegeben.
+5201,An den abgeschlossenen Handler wurde ein fehlender oder ungültiger Statusparameter übergeben.
+5202,An den abgeschlossenen Handler wurde ein fehlender oder ungültiger Absenderparameter übergeben.
+6000,Unendlich
+6001,-unendlich
+10438,Das Objekt unterstützt die Eigenschaft oder Methode "%s" nicht
+10449,Ein Argument für die Funktion "%s" ist nicht optional
+15001,"%s" ist keine Zahl
+15002,"%s" ist keine Funktion
+15004,"%s" ist kein indizierbares Objekt
+15005,"%s" ist keine Zeichenfolge
+15006,"%s" ist kein Datumsobjekt
+15007,"%s" ist Null oder kein Objekt
+15008,Zuweisung an "%s" nicht möglich
+15009,"%s" ist undefiniert
+15010,"%s" ist nicht vom Typ Boolesch
+15012,"%s" kann nicht gelöscht werden
+15013,"%s" ist nicht vom Typ VBArray
+15014,"%s" ist kein JavaScript-Objekt
+15015,"%s" ist kein Aufzählungsobjekt
+15016,"%s" ist kein Objekt mit regulärem Ausdruck
+15028,%s ist kein Array- oder Arguments-Objekt
+15031,%s ist kein Arrayobjekt
+15036,Das "%s"-Attribut für den Eigenschaftendeskriptor kann für dieses Objekt nicht auf "true" festgelegt werden
+15037,Das "%s"-Attribut für den Eigenschaftendeskriptor kann für dieses Objekt nicht auf "false" festgelegt werden
+15039,Neudeklaration der Konstante "%s"
+15041,Das Aufrufen des Löschvorgangs für "%s" ist im Strict-Modus nicht zulässig.
+15047,Die Eigenschaft "%s" eines undefinierten oder Nullverweises kann nicht festgelegt werden.
+15048,Die Eigenschaft "%s" eines undefinierten oder Nullverweises kann nicht abgerufen werden.
+15049,Die Eigenschaft "%s" eines undefinierten oder Nullverweises kann nicht gelöscht werden.
+15050,Auf die Eigenschaft "%s" kann nicht zugegriffen werden: Der Typ "VarDate" unterstützt keine benutzerdefinierten Eigenschaften
+15051,Der Wert der Eigenschaft "%s" ist kein Function-Objekt
+15052,Der Wert der Eigenschaft "%s" ist Null oder undefiniert, kein Function-Objekt
+15054,%s: 'this' ist Null oder undefiniert
+15055,%s: 'this' ist kein Objekt
+15056,%s: 'this' ist kein Function-Objekt
+15057,%s: 'this' ist kein String-Objekt
+15058,%s: 'this' ist kein Boolesch-Objekt
+15059,%s: 'this' ist kein Date-Objekt
+15060,%s: 'this' ist kein Number-Objekt
+15061,%s: 'this' ist kein VBArray-Objekt
+15062,%s: 'this' ist kein JavaScript-Objekt
+15063,%s: 'this' ist kein Enumerator-Objekt
+15064,%s: 'this' ist kein RegExp-Objekt
+15065,%s: Ungültiges Argument
+15066,%s: Das Argument ist kein Objekt
+15067,%s: Das Argument ist kein JavaScript-Objekt
+15068,%s: Das Argument ist kein Function-Objekt
+15069,%s: Das Argument ist kein VBArray-Objekt
+15070,%s: Das Argument ist Null oder undefiniert
+15071,%s: Das Argument ist kein Objekt und ist nicht Null
+15072,%s: Das Argument weist keine gültige "length"-Eigenschaft auf
+15073,%s: Array- oder Arguments-Objekt erwartet
+15074,Ungültiger Operand für "%s": Objekt erwartet
+15075,Ungültiger Operand für "%s": Funktion erwartet
+15076,Ungültiger Deskriptor für die Eigenschaft "%s"
+15077,Die Eigenschaft "%s" kann nicht definiert werden: Das Objekt ist nicht erweiterbar
+15078,Die nicht konfigurierbare Eigenschaft "%s" kann nicht neu definiert werden
+15079,Die schreibgeschützte Eigenschaft "%s" kann nicht geändert werden
+15080,Die Eigenschaft "%s" kann nicht geändert werden: 'length' ist schreibgeschützt
+15081,Die Eigenschaft "%s" kann nicht definiert werden
+15088,Erforderliches Argument %s für Datenansichtsmethode wurde nicht angegeben.
+15090,Ungültiges Konstruktorargument %s in Datenansicht.
+15091,Die Funktion "%s" kann aufgrund einer ungültigen Signatur nicht aufgerufen werden.
+15092,Auf die Eigenschaft "%s" kann aufgrund einer ungültigen Signatur nicht zugegriffen werden.
+15093,Die Laufzeitklasse "%s", die "Windows.Foundation.IPropertyValue" als Standardschnittstelle besitzt, wird nicht als Eingabeparametertyp unterstützt.
+15094,Das Objekt mit der Schnittstelle "Windows.Foundation.IPropertyValue", das den Laufzeitklassennamen "%s" besitzt, wird nicht als out-Parameter unterstützt.
+15096,%s: "this" ist kein Inspectable-Objekt.
+15097,%s: Fehler beim Konvertieren des Arguments in Typ "char"
+15098,%s: Fehler beim Konvertieren des Arguments in Typ "GUID"
+15099,%s: Fehler beim Konvertieren des Rückgabewerts in "IInspectable"
+15100,Fehler beim Konvertieren des Objekts in Struktur: Objekt fehlt erwartete Eigenschaft "%s".
+15101,Typ "%s" nicht gefunden
+15102,%s: Funktion aufgerufen mit zu wenig Argumenten
+15103,%s: Typ kann nicht konstruiert werden.
+15104,Fehler beim Konvertieren des Werts in "PropertyValue": %s nicht unterstützt von "PropertyValue"
+15105,Fehler beim Konvertieren des Werts in "IInspectable": %s nicht unterstützt von "IInspectable"
+15108,%s: Das Inspectable-Objekt "this" wurde freigegeben, Zugriff nicht möglich.
+15110,"this" ist nicht vom erwarteten Typ: %s
+15112,%s: Unerwarteter Fehler beim Abrufen von Metadateninformationen.
+32812,Das angegebene Datum wurde im aktuellen Gebietsschemakalender nicht gefunden.
diff --git a/src/sentry/data/error-locale/el-GR.txt b/src/sentry/data/error-locale/el-GR.txt
new file mode 100644
index 00000000000000..14d33fc72448fb
--- /dev/null
+++ b/src/sentry/data/error-locale/el-GR.txt
@@ -0,0 +1,289 @@
+5,Η κλήση διαδικασίας ή το όρισμα δεν είναι έγκυρα
+6,Υπερχείλιση
+7,Η μνήμη δεν επαρκεί
+9,Δείκτης εκτός περιοχής
+10,Αυτός ο πίνακας είναι σταθερός ή προσωρινά κλειδωμένος
+11,Διαίρεση δια του μηδενός
+13,Ασυμφωνία τύπων
+14,Ο χώρος της συμβολοσειράς εξαντλήθηκε
+17,Δεν είναι δυνατή η εκτέλεση της λειτουργίας που ζητήθηκε
+28,Ο χώρος της στοίβας εξαντλήθηκε
+35,Δεν έχει οριστεί το υποπρόγραμμα ή η συνάρτηση
+48,Σφάλμα κατά τη φόρτωση DLL
+51,Παρουσιάστηκε εσωτερικό σφάλμα.
+52,Ακατάλληλο όνομα αρχείου ή αριθμός
+53,Το αρχείο δεν βρέθηκε
+54,Ακατάλληλη κατάσταση αρχείου
+55,Το αρχείο είναι ήδη ανοικτό
+57,Σφάλμα εισόδου/εξόδου συσκευής
+58,Το αρχείο υπάρχει ήδη
+61,Ο δίσκος γέμισε
+62,Τα δεδομένα εισόδου ξεπέρασαν το τέλος του αρχείου
+67,Υπερβολικά μεγάλος αριθμός αρχείων
+68,Η συσκευή δεν είναι διαθέσιμη
+70,Άρνηση άδειας
+71,Ο δίσκος δεν είναι έτοιμος
+74,Δεν είναι δυνατή η μετονομασία με διαφορετική μονάδα δίσκου
+75,Παρουσιάστηκε σφάλμα κατά την πρόσβαση διαδρομής/αρχείου
+76,Η διαδρομή δεν βρέθηκε
+91,Δεν έχει οριστεί μεταβλητή αντικειμένου ή μεταβλητή μπλοκ With
+92,Δεν έχει γίνει προετοιμασία του βρόχου "For"
+94,Η χρήση του μηδενικού (Null) χαρακτήρα δεν είναι έγκυρη
+322,Δεν είναι δυνατή η δημιουργία του απαραίτητου προσωρινού αρχείου
+424,Απαιτείται αντικείμενο
+429,Δεν είναι δυνατή η δημιουργία του αντικειμένου από το διακομιστή αυτοματοποίησης
+430,Η κλάση δεν υποστηρίζει αυτοματοποίηση
+432,Το όνομα αρχείου ή κλάσης δεν βρέθηκε κατά τη λειτουργία της Αυτοματοποίησης
+438,Το αντικείμενο δεν υποστηρίζει αυτή την ιδιότητα ή μέθοδο
+440,Σφάλμα αυτοματοποίησης
+445,Το αντικείμενο δεν υποστηρίζει αυτή την ενέργεια
+446,Το αντικείμενο δεν υποστηρίζει επώνυμα ορίσματα
+447,Το αντικείμενο δεν υποστηρίζει την τρέχουσα τοπική ρύθμιση
+448,Το επώνυμο όρισμα δε βρέθηκε
+449,Το όρισμα δεν είναι προαιρετικό
+450,Εσφαλμένο πλήθος ορισμάτων ή η εκχώρηση ιδιότητας δεν είναι έγκυρη
+451,Το αντικείμενο δεν είναι συλλογή
+453,Η καθορισμένη συνάρτηση DLL δεν βρέθηκε
+458,Η μεταβλητή χρησιμοποιεί έναν τύπο αυτοματοποίησης που δεν υποστηρίζεται στη JavaScript
+462,Ο απομακρυσμένος διακομιστής δεν υπάρχει ή δεν είναι διαθέσιμος
+501,Δεν είναι δυνατή η εκχώρηση σε μεταβλητή
+502,Το αντικείμενο δεν είναι ασφαλές για δέσμη ενεργειών
+503,Το αντικείμενο δεν είναι ασφαλές για προετοιμασία
+504,Το αντικείμενο δεν είναι ασφαλές για δημιουργία
+507,Παρουσιάστηκε εξαίρεση
+1001,Η μνήμη δεν επαρκεί
+1002,Συντακτικό σφάλμα
+1003,Αναμενόμενο ':'
+1004,Αναμενόμενο ';'
+1005,Αναμενόμενο '('
+1006,Αναμενόμενο ')'
+1007,Αναμενόμενο ']'
+1008,Αναμενόμενο '{'
+1009,Αναμενόμενο '}'
+1010,Αναμενόμενο αναγνωριστικό
+1011,Αναμενόμενο '='
+1012,Αναμενόμενο '/'
+1013,Ο αριθμός δεν είναι έγκυρος
+1014,Ο χαρακτήρας δεν είναι έγκυρος
+1015,Μη τερματισμένη σταθερά συμβολοσειράς
+1016,Μη τερματισμένο σχόλιο
+1018,Πρόταση 'return' έξω από συνάρτηση
+1019,Δεν επιτρέπεται το 'break' έξω από βρόχο
+1020,Δεν επιτρέπεται το 'continue' έξω από βρόχο
+1023,Αναμενόμενο δεκαεξαδικό ψηφίο
+1024,Αναμενόμενο 'while'
+1025,Η ετικέτα ορίστηκε πάλι
+1026,Η ετικέτα δεν βρέθηκε
+1027,Το 'default' είναι δυνατό να εμφανίζεται μόνο μία φορά σε προτάσεις 'switch'
+1028,Αναμενόμενο αναγνωριστικό, συμβολοσειρά ή αριθμός
+1029,Αναμενόμενο '@end'
+1030,Η μεταγλώττιση υπό όρους είναι απενεργοποιημένη
+1031,Αναμενόμενη σταθερά
+1032,Αναμενόμενο '@'
+1033,Αναμενόμενο 'catch'
+1034,Αναμενόμενο 'var'
+1035,Το στοιχείο "throw" πρέπει να ακολουθείται από μια έκφραση στην ίδια γραμμή προέλευσης
+1037,Δηλώσεις "with" δεν επιτρέπονται σε αυστηρή λειτουργία
+1038,Στην αυστηρή λειτουργία δεν επιτρέπονται διπλότυπα ονόματα τυπικής παραμέτρου
+1039,Στην αυστηρή λειτουργία δεν επιτρέπονται οκταδικές αριθμητικές τιμές literal και χαρακτήρες διαφυγής
+1041,Μη έγκυρη χρήση του 'eval' σε αυστηρή λειτουργία
+1042,Μη έγκυρη χρήση του 'arguments' σε αυστηρή λειτουργία
+1045,Στην αυστηρή λειτουργία δεν επιτρέπεται κλήση διαγραφής σε παράσταση
+1046,Στην αυστηρή λειτουργία δεν επιτρέπονται πολλαπλοί ορισμοί μιας ιδιότητας
+1047,Στην αυστηρή λειτουργία, οι δηλώσεις συνάρτησης δεν είναι δυνατό να είναι ένθετες σε πρόταση ή μπλοκ. Ίσως εμφανίζονται μόνο σε ανώτατο επίπεδο ή απευθείας σε σώμα λειτουργίας.
+1048,Η χρήση λέξης-κλειδιού για αναγνωριστικό δεν είναι έγκυρη
+1049,Η χρήση μιας μελλοντικής δεσμευμένης λέξης για αναγνωριστικό δεν είναι έγκυρη
+1050,Η χρήση μιας μελλοντικής δεσμευμένης λέξης για αναγνωριστικό δεν είναι έγκυρη. Το αναγνωριστικό όνομα είναι δεσμευμένο στην αυστηρή λειτουργία.
+1051,Οι συναρτήσεις του setter πρέπει να έχουν ένα όρισμα
+4096,Σφάλμα μεταγλώττισης JavaScript
+4097,Σφάλμα χρόνου εκτέλεσης JavaScript
+4098,Άγνωστο σφάλμα χρόνου εκτέλεσης
+5000,Δεν είναι δυνατή η εκχώρηση σε 'this'
+5001,Αναμένεται αριθμός
+5002,Αναμένεται συνάρτηση
+5003,Δεν είναι δυνατή η εκχώρηση σε αποτέλεσμα συνάρτησης
+5004,Δεν είναι δυνατή η εκχώρηση δείκτη στο αντικείμενο
+5005,Αναμένεται συμβολοσειρά
+5006,Αναμένεται αντικείμενο ημερομηνίας
+5007,Αναμένεται αντικείμενο
+5008,Μη έγκυρη αριστερή πλευρά στην εκχώρηση
+5009,Μη καθορισμένο αναγνωριστικό
+5010,Αναμένεται δυαδική τιμή
+5011,Δεν είναι δυνατή η εκτέλεση κώδικα από ελεύθερη δέσμη ενεργειών
+5012,Αναμένεται μέλος αντικειμένου
+5013,Αναμένεται VBArray
+5014,Αναμένεται αντικείμενο JavaScript
+5015,Αναμένεται αντικείμενο απαρίθμησης
+5016,Αναμένεται αντικείμενο κανονικής έκφρασης
+5017,Συντακτικό σφάλμα σε κανονική έκφραση
+5018,Μη αναμενόμενος ποσοδείκτης
+5019,Αναμενόμενο ']' σε κανονική έκφραση
+5020,Αναμενόμενο ')' σε κανονική έκφραση
+5021,Η περιοχή σε σύνολο χαρακτήρων δεν είναι έγκυρη
+5022,Εμφανίστηκε εξαίρεση και δεν έγινε αντιληπτή από το σύστημα
+5023,Η συνάρτηση δεν έχει έγκυρο αντικείμενο πρωτοτύπου
+5024,Το URI που θα κωδικοποιηθεί περιέχει χαρακτήρα που δεν είναι έγκυρος
+5025,Το URI που θα αποκωδικοποιηθεί δεν είναι έγκυρη κωδικοποίηση
+5026,Το πλήθος των κλασματικών ψηφίων είναι εκτός περιοχής τιμών
+5027,Η ακρίβεια είναι εκτός περιοχής τιμών
+5028,Αναμένεται πίνακας ή αντικείμενο ορισμάτων
+5029,Το μήκος του πίνακα πρέπει να είναι πεπερασμένος θετικός ακέραιος
+5030,Στο μήκος του πίνακα πρέπει να αντιστοιχιστεί πεπερασμένος θετικός αριθμός
+5031,Αναμένεται αντικείμενο πίνακα
+5034,Δεν υποστηρίζεται κυκλική αναφορά σε όρισμα τιμής
+5035,Μη έγκυρο όρισμα αντικατάστασης
+5038,Η λίστα ορισμάτων είναι υπερβολικά μεγάλη για να εφαρμοστεί
+5039,Νέα δήλωση ιδιότητας const
+5041,Δεν είναι δυνατή η ρύθμιση μέλους αντικειμένου
+5042,Μη καθορισμένη μεταβλητή σε αυστηρή λειτουργία
+5043,Στην αυστηρή λειτουργία δεν επιτρέπεται η πρόσβαση στην ιδιότητα 'caller' μιας συνάρτησης ή ενός αντικειμένου ορισμάτων
+5044,Στην αυστηρή λειτουργία δεν επιτρέπεται η πρόσβαση στην ιδιότητα 'callee' ενός αντικειμένου ορισμάτων
+5045,Στην αυστηρή λειτουργία δεν επιτρέπεται ανάθεση σε ιδιότητες μόνο για ανάγνωση
+5046,Δεν είναι δυνατή η δημιουργία ιδιότητας για μη επεκτάσιμο αντικείμενο
+5047,Αναμένεται αντικείμενο
+5048,Αναμένεται αντικείμενο
+5049,Αναμένεται αντικείμενο
+5050,Αναμένεται αντικείμενο
+5051,Αναμένεται συνάρτηση
+5052,Αναμένεται συνάρτηση
+5053,Η ιδιότητα δεν μπορεί να έχει στοιχεία πρόσβασης και τιμή
+5054,Η ιδιότητα 'this' είναι null ή ακαθόριστη
+5055,Αναμένεται αντικείμενο
+5056,Αναμένεται συνάρτηση
+5057,Αναμένεται συμβολοσειρά
+5058,Αναμένεται δυαδική τιμή
+5059,Αναμενόμενη ημερομηνία
+5060,Αναμένεται αριθμός
+5061,Αναμένεται VBArray
+5062,Αναμένεται αντικείμενο JavaScript
+5063,Αναμένεται αντικείμενο απαρίθμησης
+5064,Αναμένεται αντικείμενο RegExp
+5065,Μη έγκυρο όρισμα συνάρτησης
+5066,Αναμένεται αντικείμενο
+5067,Αναμένεται αντικείμενο JavaScript
+5068,Αναμένεται συνάρτηση
+5069,Αναμένεται VBArray
+5070,Αναμένεται αντικείμενο
+5071,Αναμένεται αντικείμενο
+5072,Μη έγκυρη ιδιότητα 'length'
+5073,Αναμένεται πίνακας ή αντικείμενο ορισμάτων
+5074,Μη έγκυρος τελεστέος
+5075,Μη έγκυρος τελεστέος
+5076,Μη έγκυρη περιγραφή ιδιότητας
+5077,Δεν είναι δυνατός ο ορισμός της ιδιότητας: το αντικείμενο δεν είναι επεκτάσιμο
+5078,Δεν είναι δυνατή η επανάληψη του ορισμού της μη ρυθμιζόμενης ιδιότητας
+5079,Δεν είναι δυνατή η τροποποίηση της ιδιότητας χωρίς δυνατότητα εγγραφής
+5080,Δεν είναι δυνατή η τροποποίηση της ιδιότητας: η ιδιότητα 'length' δεν έχει δυνατότητα εγγραφής
+5081,Δεν είναι δυνατός ο ορισμός της ιδιότητας
+5082,Το όρισμα κατασκευής καθορισμένου πίνακα δεν είναι έγκυρο
+5083,Το 'this' δεν αποτελεί αντικείμενο καθορισμένου πίνακα
+5084,Μη έγκυρη μετατόπιση/μήκος κατά τη δημιουργία ενός καθορισμένου πίνακα
+5085,Μη έγκυρη τιμή έναρξης/λήξης στη μέθοδο δευτερεύοντος πίνακα καθορισμένου πίνακα
+5086,Μη έγκυρη προέλευση συνόλου καθορισμένου πίνακα
+5087,Το 'this' δεν αποτελεί αντικείμενο DataView
+5088,Μη έγκυρα ορίσματα στο DataView
+5089,Λειτουργία πρόσβασης DataView πέρα από το καθορισμένο μήκος buffer
+5090,Μη έγκυρα ορίσματα στο DataView
+5091,μη έγκυρη υπογραφή συνάρτησης
+5092,μη έγκυρη υπογραφή ιδιότητας
+5093,μη έγκυρος τύπος παραμέτρου εισόδου
+5094,μη έγκυρη παράμετρος εξόδου
+5095,Στην αυστηρή λειτουργία δεν επιτρέπεται η πρόσβαση στην ιδιότητα 'arguments' μιας συνάρτησης
+5096,Αναμένεται αντικείμενο με δυνατότητα ελέγχου
+5097,Δεν ήταν δυνατή η μετατροπή του ορίσματος σε τύπο 'char'
+5098,Δεν ήταν δυνατή η μετατροπή του ορίσματος σε τύπο 'GUID'
+5099,Αναμένεται IInspectable
+5100,Δεν ήταν δυνατή η μετατροπή αντικειμένου σε δομή: το αντικείμενο δεν διαθέτει την αναμενόμενη ιδιότητα
+5101,Άγνωστος τύπος
+5102,Έγινε κλήση συνάρτησης με πολύ λίγα ορίσματα
+5103,Ο τύπος δεν είναι κατασκευάσιμος
+5104,Δεν ήταν δυνατή η μετατροπή της τιμής σε PropertyValue: Ο τύπος δεν υποστηρίζεται από το PropertyValue
+5105,Δεν ήταν δυνατή η μετατροπή της τιμής σε IInspectable: Ο τύπος δεν υποστηρίζεται από το IInspectable
+5106,Δεν ήταν δυνατή η μετατροπή του Date σε Windows.Foundation.DateTime: η τιμή βρίσκεται εκτός της έγκυρης περιοχής
+5107,Δεν ήταν δυνατή η μετατροπή της τιμής σε Windows.Foundation.TimeSpan: η τιμή βρίσκεται εκτός της έγκυρης περιοχής
+5108,Μη έγκυρη πρόσβαση στο αντικείμενο με δυνατότητα ελέγχου το οποίο έχει ήδη εκδοθεί
+5109,Δεν είναι δυνατή η έκδοση του αντικειμένου με δυνατότητα ελέγχου το οποίο έχει ήδη εκδοθεί
+5110,"αυτό" δεν είναι του αναμενόμενου τύπου
+5111,Έχει καθοριστεί μη έγκυρο μήκος και μέγεθος πίνακα
+5112,Παρουσιάστηκε μη αναμενόμενη αποτυχία κατά την προσπάθεια απόκτησης των πληροφοριών μετα-δεδομένων
+5200,Η κατάσταση είναι "σφάλμα", αλλά το getResults δεν επέστρεψε σφάλμα
+5201,Στο ολοκληρωμένο πρόγραμμα χειρισμού διαβιβάστηκε μη έγκυρη παράμετρος κατάστασης ή παράμετρος κατάστασης που λείπει
+5202,Στο ολοκληρωμένο πρόγραμμα χειρισμού διαβιβάστηκε μη έγκυρη παράμετρος αποστολέα ή παράμετρος αποστολέα που λείπει
+6000,Άπειρο
+6001,-Άπειρο
+10438,Το αντικείμενο δεν υποστηρίζει την ιδιότητα ή μέθοδο '%s'
+10449,Το όρισμα στη συνάρτηση '%s' δεν είναι προαιρετικό
+15001,Το '%s' δεν είναι αριθμός
+15002,Το '%s' δεν είναι συνάρτηση
+15004,Το '%s' δεν είναι αντικείμενο με δυνατότητα ευρετηρίου
+15005,Το '%s' δεν είναι συμβολοσειρά
+15006,Το '%s' δεν είναι αντικείμενο ημερομηνίας
+15007,Το '%s' είναι μηδενικό ή δεν είναι αντικείμενο
+15008,Δεν είναι δυνατή η εκχώρηση στο '%s'
+15009,Το '%s' δεν έχει οριστεί
+15010,Το '%s' δεν είναι δυαδική τιμή
+15012,Δεν είναι δυνατή η διαγραφή του '%s'
+15013,Το '%s' δεν είναι VBArray
+15014,Το '%s' δεν είναι αντικείμενο JavaScript
+15015,Το '%s' δεν είναι αντικείμενο απαριθμητή
+15016,Το '%s' δεν είναι αντικείμενο κανονικής έκφρασης
+15028,Το %s δεν είναι πίνακας (Array) ή αντικείμενο ορισμάτων
+15031,Το %s δεν είναι αντικείμενο πίνακα (Array)
+15036,Το χαρακτηριστικό '%s' στην περιγραφή της ιδιότητας δεν είναι δυνατό να οριστεί σε "true" σε αυτό το αντικείμενο
+15037,Το χαρακτηριστικό '%s' στην περιγραφή της ιδιότητας δεν είναι δυνατό να οριστεί σε "false" σε αυτό το αντικείμενο
+15039,Νέα δήλωση της σταθεράς '%s'
+15041,Στην αυστηρή λειτουργία δεν επιτρέπεται κλήση διαγραφής στο '%s'
+15047,Δεν είναι δυνατός ο ορισμός της ιδιότητας ''%s" με ακαθόριστη αναφορά ή αναφορά null
+15048,Δεν είναι δυνατή η λήψη της ιδιότητας ''%s" με ακαθόριστη αναφορά ή αναφορά null
+15049,Δεν είναι δυνατή η διαγραφή της ιδιότητας ''%s" με ακαθόριστη αναφορά ή αναφορά null
+15050,Δεν είναι δυνατή η πρόσβαση στην ιδιότητα '%s': ο τύπος 'VarDate' δεν υποστηρίζει ιδιότητες που καθορίζονται από το χρήστη
+15051,Η τιμή της ιδιότητας '%s' δεν είναι αντικείμενο συνάρτησης
+15052,Η τιμή της ιδιότητας '%s' είναι null ή ακαθόριστη και όχι αντικείμενο συνάρτησης
+15054,%s: η ιδιότητα 'this' έχει τιμή null ή είναι ακαθόριστη
+15055,%s: η ιδιότητα 'this' δεν είναι αντικείμενο
+15056,%s: η ιδιότητα 'this' δεν είναι αντικείμενο συνάρτησης
+15057,%s: η ιδιότητα 'this' δεν είναι αντικείμενο συμβολοσειράς
+15058,%s: η ιδιότητα 'this' δεν είναι δυαδικό αντικείμενο
+15059,%s: η ιδιότητα 'this' δεν είναι αντικείμενο ημερομηνίας
+15060,%s: η ιδιότητα 'this' δεν είναι αντικείμενο αριθμού
+15061,%s: η ιδιότητα 'this' δεν είναι αντικείμενο VBArray
+15062,%s: η ιδιότητα 'this' δεν είναι αντικείμενο JavaScript
+15063,%s: η ιδιότητα 'this' δεν είναι αντικείμενο απαρίθμησης
+15064,%s: η ιδιότητα 'this' δεν είναι αντικείμενο RegExp
+15065,%s: μη έγκυρο όρισμα
+15066,%s: το όρισμα δεν είναι αντικείμενο
+15067,%s: το όρισμα δεν είναι αντικείμενο JavaScript
+15068,%s: το όρισμα δεν είναι αντικείμενο συνάρτησης
+15069,%s: το όρισμα δεν είναι αντικείμενο VBArray
+15070,%s: το όρισμα έχει τιμή null ή είναι ακαθόριστο
+15071,%s: το όρισμα δεν είναι αντικείμενο και δεν έχει τιμή null
+15072,%s: το όρισμα δεν έχει έγκυρη ιδιότητα 'length'
+15073,%s: Αναμένεται αντικείμενο πίνακα (Array) ή ορισμάτων
+15074,Μη έγκυρος τελεστέος στο '%s': Αναμένεται αντικείμενο
+15075,Μη έγκυρος τελεστέος στο '%s': Αναμένεται συνάρτηση
+15076,Μη έγκυρη περιγραφή για την ιδιότητα '%s'
+15077,Δεν είναι δυνατός ο ορισμός της ιδιότητας '%s': το αντικείμενο δεν είναι επεκτάσιμο
+15078,Δεν είναι δυνατή η επανάληψη του ορισμού της ιδιότητας '%s' χωρίς δυνατότητα ρύθμισης
+15079,Δεν είναι δυνατή η τροποποίηση της ιδιότητας '%s' χωρίς δυνατότητα εγγραφής
+15080,Δεν είναι δυνατή η τροποποίηση της ιδιότητας '%s': η ιδιότητα 'length' δεν έχει δυνατότητα εγγραφής
+15081,Δεν είναι δυνατός ο ορισμός της ιδιότητας '%s'
+15088,Το απαιτούμενο όρισμα %s στη μέθοδο DataView δεν έχει καθοριστεί
+15090,Το όρισμα κατασκευής DataView %s δεν είναι έγκυρο
+15091,Η λειτουργία '%s' δεν έχει έγκυρη υπογραφή και δεν είναι δυνατή η κλήση της
+15092,Η ιδιότητα '%s' δεν έχει έγκυρη υπογραφή και η πρόσβαση σε αυτή δεν είναι δυνατή
+15093,Το runtimeclass %s το οποίο έχει το Windows.Foundation.IPropertyValue ως προεπιλεγμένη διασύνδεση δεν υποστηρίζεται ως τύπος παραμέτρου εισόδου
+15094,Το αντικείμενο με διασύνδεση Windows.Foundation.IPropertyValue που έχει όνομα runtimeclass %s δεν υποστηρίζεται ως παράμετρος εξόδου
+15096,%s: το 'this' δεν αποτελεί αντικείμενο με δυνατότητα ελέγχου
+15097,%s: δεν ήταν δυνατή η μετατροπή του ορίσματος σε τύπο 'char'
+15098,%s: δεν ήταν δυνατή η μετατροπή του ορίσματος σε τύπο 'GUID'
+15099,%s: δεν ήταν δυνατή η μετατροπή της επιστρεφόμενης τιμής σε IInspectable
+15100,Δεν ήταν δυνατή η μετατροπή του αντικειμένου σε δομή: το αντικείμενο δεν διαθέτει την αναμενόμενη ιδιότητα '%s'
+15101,Ο τύπος '%s' δεν βρέθηκε
+15102,%s: έγινε κλήση συνάρτησης με πολύ λίγα ορίσματα
+15103,%s: ο τύπος δεν είναι κατασκευάσιμος
+15104,Δεν ήταν δυνατή η μετατροπή της τιμής σε PropertyValue: το %s δεν υποστηρίζεται από το PropertyValue
+15105,Δεν ήταν δυνατή η μετατροπή της τιμής σε IInspectable: το %s δεν υποστηρίζεται από το IInspectable
+15108,%s: Το αντικείμενο με δυνατότητα ελέγχου 'this' έχει εκδοθεί και δεν υπάρχει δυνατότητα πρόσβασης
+15110,"αυτό" δεν είναι αναμενόμενου τύπου: %s
+15112,%s: παρουσιάστηκε μη αναμενόμενη αποτυχία κατά την προσπάθεια απόκτησης των πληροφοριών μετα-δεδομένων
+32812,Η καθορισμένη ημερομηνία δεν είναι διαθέσιμη στο ημερολόγιο της τρέχουσας τοποθεσίας
diff --git a/src/sentry/data/error-locale/en-US.txt b/src/sentry/data/error-locale/en-US.txt
new file mode 100644
index 00000000000000..6580983d9b4a88
--- /dev/null
+++ b/src/sentry/data/error-locale/en-US.txt
@@ -0,0 +1,289 @@
+5,Invalid procedure call or argument
+6,Overflow
+7,Out of memory
+9,Subscript out of range
+10,This array is fixed or temporarily locked
+11,Division by zero
+13,Type mismatch
+14,Out of string space
+17,Can't perform requested operation
+28,Out of stack space
+35,Sub or Function not defined
+48,Error in loading DLL
+51,Internal error
+52,Bad file name or number
+53,File not found
+54,Bad file mode
+55,File already open
+57,Device I/O error
+58,File already exists
+61,Disk full
+62,Input past end of file
+67,Too many files
+68,Device unavailable
+70,Permission denied
+71,Disk not ready
+74,Can't rename with different drive
+75,Path/File access error
+76,Path not found
+91,Object variable or With block variable not set
+92,For loop not initialized
+94,Invalid use of Null
+322,Can't create necessary temporary file
+424,Object required
+429,Automation server can't create object
+430,Class doesn't support Automation
+432,File name or class name not found during Automation operation
+438,Object doesn't support this property or method
+440,Automation error
+445,Object doesn't support this action
+446,Object doesn't support named arguments
+447,Object doesn't support current locale setting
+448,Named argument not found
+449,Argument not optional
+450,Wrong number of arguments or invalid property assignment
+451,Object not a collection
+453,Specified DLL function not found
+458,Variable uses an Automation type not supported in JavaScript
+462,The remote server machine does not exist or is unavailable
+501,Cannot assign to variable
+502,Object not safe for scripting
+503,Object not safe for initializing
+504,Object not safe for creating
+507,An exception occurred
+1001,Out of memory
+1002,Syntax error
+1003,Expected ':'
+1004,Expected ';'
+1005,Expected '('
+1006,Expected ')'
+1007,Expected ']'
+1008,Expected '{'
+1009,Expected '}'
+1010,Expected identifier
+1011,Expected '='
+1012,Expected '/'
+1013,Invalid number
+1014,Invalid character
+1015,Unterminated string constant
+1016,Unterminated comment
+1018,'return' statement outside of function
+1019,Can't have 'break' outside of loop
+1020,Can't have 'continue' outside of loop
+1023,Expected hexadecimal digit
+1024,Expected 'while'
+1025,Label redefined
+1026,Label not found
+1027,'default' can only appear once in a 'switch' statement
+1028,Expected identifier, string or number
+1029,Expected '@end'
+1030,Conditional compilation is turned off
+1031,Expected constant
+1032,Expected '@'
+1033,Expected 'catch'
+1034,Expected 'var'
+1035,'throw' must be followed by an expression on the same source line
+1037,'with' statements are not allowed in strict mode
+1038,Duplicate formal parameter names not allowed in strict mode
+1039,Octal numeric literals and escape characters not allowed in strict mode
+1041,Invalid usage of 'eval' in strict mode
+1042,Invalid usage of 'arguments' in strict mode
+1045,Calling delete on expression not allowed in strict mode
+1046,Multiple definitions of a property not allowed in strict mode
+1047,In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.
+1048,The use of a keyword for an identifier is invalid
+1049,The use of a future reserved word for an identifier is invalid
+1050,The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.
+1051,Setter functions must have one argument
+4096,JavaScript compilation error
+4097,JavaScript runtime error
+4098,Unknown runtime error
+5000,Cannot assign to 'this'
+5001,Number expected
+5002,Function expected
+5003,Cannot assign to a function result
+5004,Cannot index object
+5005,String expected
+5006,Date object expected
+5007,Object expected
+5008,Invalid left-hand side in assignment
+5009,Undefined identifier
+5010,Boolean expected
+5011,Can't execute code from a freed script
+5012,Object member expected
+5013,VBArray expected
+5014,JavaScript object expected
+5015,Enumerator object expected
+5016,Regular Expression object expected
+5017,Syntax error in regular expression
+5018,Unexpected quantifier
+5019,Expected ']' in regular expression
+5020,Expected ')' in regular expression
+5021,Invalid range in character set
+5022,Exception thrown and not caught
+5023,Function does not have a valid prototype object
+5024,The URI to be encoded contains an invalid character
+5025,The URI to be decoded is not a valid encoding
+5026,The number of fractional digits is out of range
+5027,The precision is out of range
+5028,Array or arguments object expected
+5029,Array length must be a finite positive integer
+5030,Array length must be assigned a finite positive number
+5031,Array object expected
+5034,Circular reference in value argument not supported
+5035,Invalid replacer argument
+5038,Argument list too large to apply
+5039,Redeclaration of const property
+5041,Object member not configurable
+5042,Variable undefined in strict mode
+5043,Accessing the 'caller' property of a function or arguments object is not allowed in strict mode
+5044,Accessing the 'callee' property of an arguments object is not allowed in strict mode
+5045,Assignment to read-only properties is not allowed in strict mode
+5046,Cannot create property for a non-extensible object
+5047,Object expected
+5048,Object expected
+5049,Object expected
+5050,Object expected
+5051,Function expected
+5052,Function expected
+5053,Property cannot have both accessors and a value
+5054,'this' is null or undefined
+5055,Object expected
+5056,Function expected
+5057,String expected
+5058,Boolean expected
+5059,Date expected
+5060,Number expected
+5061,VBArray expected
+5062,JavaScript object expected
+5063,Enumerator object expected
+5064,RegExp object expected
+5065,Invalid function argument
+5066,Object expected
+5067,JavaScript object expected
+5068,Function expected
+5069,VBArray expected
+5070,Object expected
+5071,Object expected
+5072,Invalid 'length' property
+5073,Array or arguments object expected
+5074,Invalid Operand
+5075,Invalid Operand
+5076,Invalid property descriptor
+5077,Cannot define property: object is not extensible
+5078,Cannot redefine non-configurable property
+5079,Cannot modify non-writable property
+5080,Cannot modify property: 'length' is not writable
+5081,Cannot define property
+5082,Typed array constructor argument is invalid
+5083,'this' is not a typed array object
+5084,Invalid offset/length when creating typed array
+5085,Invalid begin/end value in typed array subarray method
+5086,Invalid source in typed array set
+5087,'this' is not a DataView object
+5088,Invalid arguments in DataView
+5089,DataView operation access beyond specified buffer length
+5090,Invalid arguments in DataView
+5091,invalid function signature
+5092,invalid property signature
+5093,invalid input parameter type
+5094,invalid ouput parameter
+5095,Accessing the 'arguments' property of a function is not allowed in strict mode
+5096,Inspectable Object expected
+5097,Could not convert argument to type 'char'
+5098,Could not convert argument to type 'GUID'
+5099,IInspectable expected
+5100,Could not convert object to struct: object missing expected property
+5101,Unknown type
+5102,Function called with too few arguments
+5103,Type is not constructible
+5104,Could not convert value to PropertyValue: Type not supported by PropertyValue
+5105,Could not convert value to IInspectable: Type not supported by IInspectable
+5106,Could not convert Date to Windows.Foundation.DateTime: value outside of valid range
+5107,Could not convert value to Windows.Foundation.TimeSpan: value outside of valid range
+5108,Invalid access to already released Inspectable Object
+5109,Cannot release already released Inspectable Object
+5110,'this' is not of the expected type
+5111,Illegal length and size specified for the array
+5112,An unexpected failure occurred while trying to obtain metadata information
+5200,Status is 'error', but getResults did not return an error
+5201,Missing or invalid status parameter passed to completed handler
+5202,Missing or invalid sender parameter passed to completed handler
+6000,Infinity
+6001,-Infinity
+10438,Object doesn't support property or method '%s'
+10449,Argument to the function '%s' is not optional
+15001,'%s' is not a number
+15002,'%s' is not a function
+15004,'%s' is not an indexable object
+15005,'%s' is not a string
+15006,'%s' is not a date object
+15007,'%s' is null or not an object
+15008,Cannot assign to '%s'
+15009,'%s' is undefined
+15010,'%s' is not a boolean
+15012,Cannot delete '%s'
+15013,'%s' is not a VBArray
+15014,'%s' is not a JavaScript object
+15015,'%s' is not an enumerator object
+15016,'%s' is not a regular expression object
+15028,%s is not an Array or arguments object
+15031,%s is not an Array object
+15036,'%s' attribute on the property descriptor cannot be set to 'true' on this object
+15037,'%s' attribute on the property descriptor cannot be set to 'false' on this object
+15039,Redeclaration of const '%s'
+15041,Calling delete on '%s' is not allowed in strict mode
+15047,Unable to set property '%s' of undefined or null reference
+15048,Unable to get property '%s' of undefined or null reference
+15049,Unable to delete property '%s' of undefined or null reference
+15050,Unable to access property '%s': type 'VarDate' does not support user-defined properties
+15051,The value of the property '%s' is not a Function object
+15052,The value of the property '%s' is null or undefined, not a Function object
+15054,%s: 'this' is null or undefined
+15055,%s: 'this' is not an Object
+15056,%s: 'this' is not a Function object
+15057,%s: 'this' is not a String object
+15058,%s: 'this' is not a Boolean object
+15059,%s: 'this' is not a Date object
+15060,%s: 'this' is not a Number object
+15061,%s: 'this' is not a VBArray object
+15062,%s: 'this' is not a JavaScript object
+15063,%s: 'this' is not an Enumerator object
+15064,%s: 'this' is not a RegExp object
+15065,%s: invalid argument
+15066,%s: argument is not an Object
+15067,%s: argument is not a JavaScript object
+15068,%s: argument is not a Function object
+15069,%s: argument is not a VBArray object
+15070,%s: argument is null or undefined
+15071,%s: argument is not an Object and is not null
+15072,%s: argument does not have a valid 'length' property
+15073,%s: Array or arguments object expected
+15074,Invalid operand to '%s': Object expected
+15075,Invalid operand to '%s': Function expected
+15076,Invalid descriptor for property '%s'
+15077,Cannot define property '%s': object is not extensible
+15078,Cannot redefine non-configurable property '%s'
+15079,Cannot modify non-writable property '%s'
+15080,Cannot modify property '%s': 'length' is not writable
+15081,Cannot define property '%s'
+15088,Required argument %s in DataView method is not specified
+15090,DataView constructor argument %s is invalid
+15091,The function '%s' has an invalid signature and cannot be called
+15092,The property '%s' has an invalid signature and cannot be accessed
+15093,The runtimeclass %s that has Windows.Foundation.IPropertyValue as default interface is not supported as input parameter type
+15094,The object with interface Windows.Foundation.IPropertyValue that has runtimeclass name %s is not supported as out parameter
+15096,%s: 'this' is not an Inspectable Object
+15097,%s: could not convert argument to type 'char'
+15098,%s: could not convert argument to type 'GUID'
+15099,%s: could not convert return value to IInspectable
+15100,Could not convert object to struct: object missing expected property '%s'
+15101,Type '%s' not found
+15102,%s: function called with too few arguments
+15103,%s: type is not constructible
+15104,Could not convert value to PropertyValue: %s not supported by PropertyValue
+15105,Could not convert value to IInspectable: %s not supported by IInspectable
+15108,%s: The Inspectable object 'this' is released and cannot be accessed
+15110,'this' is not of expected type: %s
+15112,%s: an unexpected failure occurred while trying to obtain metadata information
+32812,The specified date is not available in the current locale's calendar
diff --git a/src/sentry/data/error-locale/es-ES.txt b/src/sentry/data/error-locale/es-ES.txt
new file mode 100644
index 00000000000000..a00230b5844b07
--- /dev/null
+++ b/src/sentry/data/error-locale/es-ES.txt
@@ -0,0 +1,289 @@
+5,Argumento o llamada a procedimiento no válidos
+6,Desbordamiento
+7,Memoria insuficiente
+9,El subíndice está fuera del intervalo
+10,La matriz está bloqueada de manera fija o temporal
+11,División por cero
+13,No coinciden los tipos
+14,No hay suficiente espacio de cadena
+17,No se puede realizar la operación solicitada
+28,No hay suficiente espacio de pila
+35,No se ha definido Sub o Function
+48,Error al cargar la biblioteca DLL
+51,Error interno
+52,Nombre o número de archivo incorrecto
+53,Archivo no encontrado
+54,Modo de archivo incorrecto
+55,El archivo ya está abierto
+57,Error de E/S de dispositivo
+58,El archivo ya existe
+61,Disco lleno
+62,Se sobrepasó el final del archivo
+67,Hay demasiados archivos
+68,El dispositivo no está disponible
+70,Permiso denegado
+71,Disco no preparado
+74,No se puede cambiar el nombre con una unidad de disco diferente
+75,Error de acceso a la ruta o al archivo
+76,Ruta de acceso no encontrada
+91,Variable de objeto o bloque With no establecido
+92,Bucle For no inicializado
+94,Uso no válido de Null
+322,No se puede crear un archivo temporal necesario
+424,Se requiere un objeto
+429,El servidor de Automatización no puede crear el objeto
+430,Esta clase no acepta Automatización
+432,No se encontró el nombre del archivo o de la clase durante la operación de Automatización
+438,El objeto no acepta esta propiedad o método
+440,Error de Automatización
+445,El objeto no acepta esta acción
+446,El objeto no acepta argumentos con nombre
+447,El objeto no acepta la configuración regional actual
+448,Argumento con nombre no encontrado
+449,Argumento no opcional
+450,Número de argumentos erróneo o asignación de propiedad no válida
+451,El objeto no es una colección
+453,No se encuentra la función de biblioteca DLL especificada
+458,La variable usa un tipo de Automatización no aceptado en JavaScript
+462,El servidor remoto no existe o no está disponible
+501,No se puede asignar a una variable
+502,Objeto no seguro para secuencias de comandos
+503,Objeto no seguro para inicialización
+504,Objeto no seguro para creación
+507,Error de excepción
+1001,Memoria insuficiente
+1002,Error de sintaxis
+1003,Se esperaba ':'
+1004,Se esperaba ';'
+1005,Se esperaba '('
+1006,Se esperaba ')'
+1007,Se esperaba ']'
+1008,Se esperaba '{'
+1009,Se esperaba '}'
+1010,Se esperaba un identificador
+1011,Se esperaba '='
+1012,Se esperaba '/'
+1013,Número no válido
+1014,Carácter no válido
+1015,Constante de cadena sin terminar
+1016,Comentario sin terminar
+1018,La instrucción 'return' está fuera de una función
+1019,No puede haber 'break' fuera de un bucle
+1020,No puede haber 'continue' fuera de un bucle
+1023,Se esperaba un dígito hexadecimal
+1024,Se esperaba 'while'
+1025,Ya se definió la etiqueta
+1026,Etiqueta no encontrada
+1027,'default' solo puede aparecer una vez en una instrucción 'switch'
+1028,Se esperaba un identificador, una cadena o un número
+1029,Se esperaba '@end'
+1030,La compilación condicional está desactivada
+1031,Se esperaba una constante
+1032,Se esperaba '@'
+1033,Se esperaba 'catch'
+1034,Se esperaba 'var'
+1035,'throw' debe seguir una expresión en la misma línea de origen
+1037,'with' instrucciones no permitidas en modo estricto
+1038,Nombres formales duplicados de parámetros no permitidos en modo estricto
+1039,Literales numéricos octales o carácter de escape no permitidos en modo estricto
+1041,Uso no válido de 'eval' en modo estricto
+1042,Uso no válido de 'argumentos' en modo estricto
+1045,Solicitar eliminación de expresiones no permitidas en modo estricto
+1046,Definiciones múltiples de una propiedad no permitida en modo estricto
+1047,En modo estricto, las declaraciones de función no se pueden anidar dentro de una instrucción o un bloque. Solo pueden aparecer en el nivel superior o directamente dentro del cuerpo de la función.
+1048,El uso de una palabra clave para un identificador no es válido
+1049,El uso de una palabra reservada futura para un identificador no es válido
+1050,El uso de una palabra reservada futura para un identificador no es válido. El nombre del identificador es reservado en modo estricto.
+1051,Las funciones de setter deben tener un argumento
+4096,Error de compilación de JavaScript
+4097,Error en tiempo de ejecución de JavaScript
+4098,Error desconocido en tiempo de ejecución
+5000,No se puede asignar a 'this'
+5001,Se esperaba un número
+5002,Se esperaba una función
+5003,No se puede asignar al resultado de una función
+5004,No se puede indizar el objeto
+5005,Se esperaba una cadena
+5006,Se esperaba un objeto de fecha
+5007,Se esperaba un objeto
+5008,Lado izquierdo no válido en asignación
+5009,Identificador no definido
+5010,Se esperaba un tipo booleano
+5011,No se puede ejecutar código de una secuencia de comandos liberada
+5012,Se esperaba un objeto miembro
+5013,Se esperaba VBArray
+5014,Se esperaba un objeto JScript
+5015,Intervalo no válido en el juego de caracteres
+5016,Se esperaba un objeto expresión regular
+5017,Error de sintaxis en expresión regular
+5018,Cuantificador inesperado
+5019,Se esperaba ']' en la expresión regular
+5020,Se esperaba ')' en la expresión regular
+5021,Intervalo no válido en el juego de caracteres
+5022,Excepción lanzada y no recogida
+5023,La función no tiene un objeto prototipo válido
+5024,El identificador URI para codificar contiene un carácter no válido
+5025,El identificador URI para descodificar no tiene una codificación válida
+5026,El número de dígitos fraccionarios está fuera del intervalo
+5027,La precisión está fuera del intervalo
+5028,Se esperaba un objeto array o arguments
+5029,La longitud de la matriz debe ser un valor entero finito positivo
+5030,La longitud de la matriz debe ser un número positivo finito
+5031,Se esperaba un objeto Array
+5034,Referencia circular en un argumento de valor no admitida
+5035,Argumento reemplazante no válido
+5038,La lista de argumentos es demasiado extensa para aplicarse
+5039,Nueva declaración de la propiedad const
+5041,Objeto miembro no configurable
+5042,Variable sin definir en modo estricto
+5043,El acceso a la propiedad 'autor de la llamada' de una función u objeto de argumentos no está permitido en modo estricto
+5044,El acceso a la propiedad 'destinatario de la llamada' de un objeto de argumentos no está permitido en modo estricto
+5045,La asignación a propiedades de solo lectura no está permitida en modo estricto
+5046,No se puede crear una propiedad para un objeto no extensible
+5047,Se esperaba un objeto
+5048,Se esperaba un objeto
+5049,Se esperaba un objeto
+5050,Se esperaba un objeto
+5051,Se esperaba una función
+5052,Se esperaba una función
+5053,La propiedad no puede tener descriptores de acceso y un valor
+5054,'this' es nulo o está sin definir
+5055,Se esperaba un objeto
+5056,Se esperaba una función
+5057,Se esperaba una cadena
+5058,Se esperaba un tipo booleano
+5059,Se esperaba una fecha
+5060,Se esperaba un número
+5061,Se esperaba VBArray
+5062,Se esperaba un objeto JScript
+5063,Intervalo no válido en el juego de caracteres
+5064,Se esperaba un objeto RegExp
+5065,Argumento de la función no válido
+5066,Se esperaba un objeto
+5067,Se esperaba un objeto JScript
+5068,Se esperaba una función
+5069,Se esperaba VBArray
+5070,Se esperaba un objeto
+5071,Se esperaba un objeto
+5072,Propiedad de 'longitud' no válida
+5073,Se esperaba un objeto matriz o argumentos
+5074,Operando no válido
+5075,Operando no válido
+5076,Descriptor de propiedad no válido
+5077,No se puede definir la propiedad: el objeto no es extensible
+5078,No se puede redefinir la propiedad no configurable
+5079,No se puede modificar la propiedad que no permite escritura
+5080,No se puede modificar la propiedad: 'longitud' no permite escritura
+5081,No se puede definir la propiedad
+5082,Argumento de constructor de matriz con tipo no válido
+5083,'esto' no es un objeto de matriz con tipo
+5084,Longitud o desplazamiento no válido al crear matriz con tipo
+5085,Valor inicial o final no válido en método de su matriz de matriz con tipo
+5086,Origen no válido en conjunto de matriz con tipo
+5087,'esto' no es un objeto DataView
+5088,Argumentos no válidos en DataView
+5089,Acceso a operación DataView más allá de la longitud de búfer especificada
+5090,Argumentos no válidos en DataView
+5091,firma de función no válida
+5092,firma de propiedad no válida
+5093,tipo de parámetro de entrada no válido
+5094,parámetro de entrada no válido
+5095,El acceso a la propiedad 'argumentos' de una función no está permitido en modo estricto
+5096,Se espera objeto inspeccionable
+5097,No pudo convertirse el argumento al tipo 'char'
+5098,No pudo convertirse el argumento al tipo 'GUID'
+5099,Se esperaba IInspectable
+5100,No pudo convertirse el objeto a struct: objeto sin la propiedad esperada
+5101,Tipo desconocido
+5102,Función llamada con muy pocos argumentos
+5103,El tipo no es construible
+5104,No pudo convertirse el valor a PropertyValue: Tipo no admitido por PropertyValue
+5105,No pudo convertirse el valor a IInspectable: Tipo no admitido por IInspectable
+5106,No pudo convertirse la fecha a Windows.Foundation.DateTime: valor fuera de rango válido
+5107,No pudo convertirse el valor a Windows.Foundation.TimeSpan: valor fuera de rango válido
+5108,Acceso no válido a objeto inspeccionable ya distribuido
+5109,No puede distribuirse un objeto inspeccionable ya distribuido
+5110,'esto' no es del tipo esperado
+5111,Longitud y tamaño no válidos especificados para la matriz
+5112,Error inesperado al intentar obtener información de metadatos
+5200,El estado es 'error', pero getResults no obtuvo ningún error
+5201,Se pasó un parámetro status que falta o no es válido al controlador completado
+5202,Se pasó un parámetro sender que falta o no es válido al controlador completado
+6000,Infinito
+6001,-Infinito
+10438,El objeto no acepta la propiedad o el método '%s'
+10449,El argumento de la función '%s' no es opcional
+15001,'%s' no es un número
+15002,'%s' no es una función
+15004,'%s' no es un objeto indizable
+15005,'%s' no es una cadena
+15006,'%s' no es un objeto de fecha
+15007,'%s' es nulo o no es un objeto
+15008,No se puede asignar a '%s'
+15009,'%s' no está definido
+15010,'%s' no es un tipo booleano
+15012,No se puede eliminar '%s'
+15013,'%s' no es de tipo VBArray
+15014,'%s' no es un objeto JavaScript
+15015,'%s' no es un objeto enumerador
+15016,'%s' no es un objeto expresión regular
+15028,%s no es un objeto Array o arguments
+15031,%s no es un objeto Array
+15036,El atributo '%s' del descriptor de propiedad no se puede establecer en 'true' en este objeto
+15037,El atributo '%s' del descriptor de propiedad no se puede establecer en 'false' en este objeto
+15039,Nueva declaración de const '%s'
+15041,Solicitar la eliminación de '%s' no está permitida en modo estricto
+15047,No se puede establecer la propiedad '%s' de referencia nula o sin definir
+15048,No se puede obtener la propiedad '%s' de referencia nula o sin definir
+15049,No se puede eliminar la propiedad '%s' de referencia nula o sin definir
+15050,No se puede obtener acceso a la propiedad '%s': tipo 'VarDate' no es compatible con las propiedades definidas por el usuario
+15051,El valor de la propiedad '%s' no es un objeto de función
+15052,El valor de la propiedad '%s' es nulo o no está definido, no es un objeto de función
+15054,%s: 'this' es nulo o está sin definir
+15055,%s: 'this' no es un objeto
+15056,%s: 'this' no es un objeto de función
+15057,%s: 'this' no es un objeto de cadena
+15058,%s: 'this' no es un objeto booleano
+15059,%s: 'this' no es un objeto de fecha
+15060,%s: 'this' no es un objeto de número
+15061,%s: 'this' no es un objeto VBArray
+15062,%s: 'this' no es un objeto JavaScript
+15063,%s: 'this' no es un objeto enumerador
+15064,%s: 'this' no es un objeto RegExp
+15065,%s: argumento no válido
+15066,%s: el argumento no es un objeto
+15067,%s: el argumento no es un objeto JavaScript
+15068,%s: el argumento no es un objeto de función
+15069,%s: el argumento no es un objeto VBArray
+15070,%s: el argumento es nulo o está sin definir
+15071,%s: el argumento no es un objeto y no es nulo
+15072,%s: el argumento no tiene una propiedad de 'longitud' válida
+15073,%s: se esperaba un objeto de matriz o argumentos
+15074,Operando no válido para '%s': se esperaba un objeto
+15075,Operando no válido para '%s': se esperaba una función
+15076,Descriptor no válido para la propiedad '%s'
+15077,No se puede definir la propiedad '%s': el objeto no es extensible
+15078,No se puede redefinir la propiedad no configurable '%s'
+15079,No se puede modificar la propiedad que no permite escritura '%s'
+15080,No se puede modificar la propiedad '%s': 'longitud' no permite escritura
+15081,No se puede definir la propiedad '%s'
+15088,El argumento necesario %s en el método DataView no se ha especificado
+15090,El argumento de constructor %s de DataView no es válido
+15091,La función '%s' tiene una firma no válida y no se puede llamar
+15092,La propiedad '%s' tiene una firma no válida y no se puede obtener acceso a ella
+15093,La runtimeclass %s con Windows.Foundation.IPropertyValue como interfaz predeterminada no es compatible como tipo de parámetro de entrada
+15094,El objeto con la interfaz Windows.Foundation.IPropertyValue y el nombre de runtimeclass %s no es compatible como parámetro de salida
+15096,%s: 'this' no es un objeto inspeccionable
+15097,%s: no pudo convertirse el argumento al tipo 'char'
+15098,%s: no pudo convertirse el argumento al tipo 'GUID'
+15099,%s: no pudo convertirse el valor devuelto a IInspectable
+15100,No pudo convertirse el objeto a struct: objeto sin la propiedad esperada '%s'
+15101,Tipo '%s' no encontrado
+15102,%s: función llamada con muy pocos argumentos
+15103,%s: tipo no construible
+15104,No pudo convertirse el valor a PropertyValue: PropertyValue no admite %s
+15105,No pudo convertirse el valor a IInspectable: IInspectable no admite %s
+15108,%s: el objeto inspeccionable 'this' ya se distribuyó y no puede obtenerse acceso a él
+15110,'esto' no es del tipo esperado: %s
+15112,%s: error inesperado al intentar obtener información de metadatos
+32812,La fecha especificada no está disponible en el calendario de la configuración regional actual
diff --git a/src/sentry/data/error-locale/et-EE.txt b/src/sentry/data/error-locale/et-EE.txt
new file mode 100644
index 00000000000000..6580983d9b4a88
--- /dev/null
+++ b/src/sentry/data/error-locale/et-EE.txt
@@ -0,0 +1,289 @@
+5,Invalid procedure call or argument
+6,Overflow
+7,Out of memory
+9,Subscript out of range
+10,This array is fixed or temporarily locked
+11,Division by zero
+13,Type mismatch
+14,Out of string space
+17,Can't perform requested operation
+28,Out of stack space
+35,Sub or Function not defined
+48,Error in loading DLL
+51,Internal error
+52,Bad file name or number
+53,File not found
+54,Bad file mode
+55,File already open
+57,Device I/O error
+58,File already exists
+61,Disk full
+62,Input past end of file
+67,Too many files
+68,Device unavailable
+70,Permission denied
+71,Disk not ready
+74,Can't rename with different drive
+75,Path/File access error
+76,Path not found
+91,Object variable or With block variable not set
+92,For loop not initialized
+94,Invalid use of Null
+322,Can't create necessary temporary file
+424,Object required
+429,Automation server can't create object
+430,Class doesn't support Automation
+432,File name or class name not found during Automation operation
+438,Object doesn't support this property or method
+440,Automation error
+445,Object doesn't support this action
+446,Object doesn't support named arguments
+447,Object doesn't support current locale setting
+448,Named argument not found
+449,Argument not optional
+450,Wrong number of arguments or invalid property assignment
+451,Object not a collection
+453,Specified DLL function not found
+458,Variable uses an Automation type not supported in JavaScript
+462,The remote server machine does not exist or is unavailable
+501,Cannot assign to variable
+502,Object not safe for scripting
+503,Object not safe for initializing
+504,Object not safe for creating
+507,An exception occurred
+1001,Out of memory
+1002,Syntax error
+1003,Expected ':'
+1004,Expected ';'
+1005,Expected '('
+1006,Expected ')'
+1007,Expected ']'
+1008,Expected '{'
+1009,Expected '}'
+1010,Expected identifier
+1011,Expected '='
+1012,Expected '/'
+1013,Invalid number
+1014,Invalid character
+1015,Unterminated string constant
+1016,Unterminated comment
+1018,'return' statement outside of function
+1019,Can't have 'break' outside of loop
+1020,Can't have 'continue' outside of loop
+1023,Expected hexadecimal digit
+1024,Expected 'while'
+1025,Label redefined
+1026,Label not found
+1027,'default' can only appear once in a 'switch' statement
+1028,Expected identifier, string or number
+1029,Expected '@end'
+1030,Conditional compilation is turned off
+1031,Expected constant
+1032,Expected '@'
+1033,Expected 'catch'
+1034,Expected 'var'
+1035,'throw' must be followed by an expression on the same source line
+1037,'with' statements are not allowed in strict mode
+1038,Duplicate formal parameter names not allowed in strict mode
+1039,Octal numeric literals and escape characters not allowed in strict mode
+1041,Invalid usage of 'eval' in strict mode
+1042,Invalid usage of 'arguments' in strict mode
+1045,Calling delete on expression not allowed in strict mode
+1046,Multiple definitions of a property not allowed in strict mode
+1047,In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.
+1048,The use of a keyword for an identifier is invalid
+1049,The use of a future reserved word for an identifier is invalid
+1050,The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.
+1051,Setter functions must have one argument
+4096,JavaScript compilation error
+4097,JavaScript runtime error
+4098,Unknown runtime error
+5000,Cannot assign to 'this'
+5001,Number expected
+5002,Function expected
+5003,Cannot assign to a function result
+5004,Cannot index object
+5005,String expected
+5006,Date object expected
+5007,Object expected
+5008,Invalid left-hand side in assignment
+5009,Undefined identifier
+5010,Boolean expected
+5011,Can't execute code from a freed script
+5012,Object member expected
+5013,VBArray expected
+5014,JavaScript object expected
+5015,Enumerator object expected
+5016,Regular Expression object expected
+5017,Syntax error in regular expression
+5018,Unexpected quantifier
+5019,Expected ']' in regular expression
+5020,Expected ')' in regular expression
+5021,Invalid range in character set
+5022,Exception thrown and not caught
+5023,Function does not have a valid prototype object
+5024,The URI to be encoded contains an invalid character
+5025,The URI to be decoded is not a valid encoding
+5026,The number of fractional digits is out of range
+5027,The precision is out of range
+5028,Array or arguments object expected
+5029,Array length must be a finite positive integer
+5030,Array length must be assigned a finite positive number
+5031,Array object expected
+5034,Circular reference in value argument not supported
+5035,Invalid replacer argument
+5038,Argument list too large to apply
+5039,Redeclaration of const property
+5041,Object member not configurable
+5042,Variable undefined in strict mode
+5043,Accessing the 'caller' property of a function or arguments object is not allowed in strict mode
+5044,Accessing the 'callee' property of an arguments object is not allowed in strict mode
+5045,Assignment to read-only properties is not allowed in strict mode
+5046,Cannot create property for a non-extensible object
+5047,Object expected
+5048,Object expected
+5049,Object expected
+5050,Object expected
+5051,Function expected
+5052,Function expected
+5053,Property cannot have both accessors and a value
+5054,'this' is null or undefined
+5055,Object expected
+5056,Function expected
+5057,String expected
+5058,Boolean expected
+5059,Date expected
+5060,Number expected
+5061,VBArray expected
+5062,JavaScript object expected
+5063,Enumerator object expected
+5064,RegExp object expected
+5065,Invalid function argument
+5066,Object expected
+5067,JavaScript object expected
+5068,Function expected
+5069,VBArray expected
+5070,Object expected
+5071,Object expected
+5072,Invalid 'length' property
+5073,Array or arguments object expected
+5074,Invalid Operand
+5075,Invalid Operand
+5076,Invalid property descriptor
+5077,Cannot define property: object is not extensible
+5078,Cannot redefine non-configurable property
+5079,Cannot modify non-writable property
+5080,Cannot modify property: 'length' is not writable
+5081,Cannot define property
+5082,Typed array constructor argument is invalid
+5083,'this' is not a typed array object
+5084,Invalid offset/length when creating typed array
+5085,Invalid begin/end value in typed array subarray method
+5086,Invalid source in typed array set
+5087,'this' is not a DataView object
+5088,Invalid arguments in DataView
+5089,DataView operation access beyond specified buffer length
+5090,Invalid arguments in DataView
+5091,invalid function signature
+5092,invalid property signature
+5093,invalid input parameter type
+5094,invalid ouput parameter
+5095,Accessing the 'arguments' property of a function is not allowed in strict mode
+5096,Inspectable Object expected
+5097,Could not convert argument to type 'char'
+5098,Could not convert argument to type 'GUID'
+5099,IInspectable expected
+5100,Could not convert object to struct: object missing expected property
+5101,Unknown type
+5102,Function called with too few arguments
+5103,Type is not constructible
+5104,Could not convert value to PropertyValue: Type not supported by PropertyValue
+5105,Could not convert value to IInspectable: Type not supported by IInspectable
+5106,Could not convert Date to Windows.Foundation.DateTime: value outside of valid range
+5107,Could not convert value to Windows.Foundation.TimeSpan: value outside of valid range
+5108,Invalid access to already released Inspectable Object
+5109,Cannot release already released Inspectable Object
+5110,'this' is not of the expected type
+5111,Illegal length and size specified for the array
+5112,An unexpected failure occurred while trying to obtain metadata information
+5200,Status is 'error', but getResults did not return an error
+5201,Missing or invalid status parameter passed to completed handler
+5202,Missing or invalid sender parameter passed to completed handler
+6000,Infinity
+6001,-Infinity
+10438,Object doesn't support property or method '%s'
+10449,Argument to the function '%s' is not optional
+15001,'%s' is not a number
+15002,'%s' is not a function
+15004,'%s' is not an indexable object
+15005,'%s' is not a string
+15006,'%s' is not a date object
+15007,'%s' is null or not an object
+15008,Cannot assign to '%s'
+15009,'%s' is undefined
+15010,'%s' is not a boolean
+15012,Cannot delete '%s'
+15013,'%s' is not a VBArray
+15014,'%s' is not a JavaScript object
+15015,'%s' is not an enumerator object
+15016,'%s' is not a regular expression object
+15028,%s is not an Array or arguments object
+15031,%s is not an Array object
+15036,'%s' attribute on the property descriptor cannot be set to 'true' on this object
+15037,'%s' attribute on the property descriptor cannot be set to 'false' on this object
+15039,Redeclaration of const '%s'
+15041,Calling delete on '%s' is not allowed in strict mode
+15047,Unable to set property '%s' of undefined or null reference
+15048,Unable to get property '%s' of undefined or null reference
+15049,Unable to delete property '%s' of undefined or null reference
+15050,Unable to access property '%s': type 'VarDate' does not support user-defined properties
+15051,The value of the property '%s' is not a Function object
+15052,The value of the property '%s' is null or undefined, not a Function object
+15054,%s: 'this' is null or undefined
+15055,%s: 'this' is not an Object
+15056,%s: 'this' is not a Function object
+15057,%s: 'this' is not a String object
+15058,%s: 'this' is not a Boolean object
+15059,%s: 'this' is not a Date object
+15060,%s: 'this' is not a Number object
+15061,%s: 'this' is not a VBArray object
+15062,%s: 'this' is not a JavaScript object
+15063,%s: 'this' is not an Enumerator object
+15064,%s: 'this' is not a RegExp object
+15065,%s: invalid argument
+15066,%s: argument is not an Object
+15067,%s: argument is not a JavaScript object
+15068,%s: argument is not a Function object
+15069,%s: argument is not a VBArray object
+15070,%s: argument is null or undefined
+15071,%s: argument is not an Object and is not null
+15072,%s: argument does not have a valid 'length' property
+15073,%s: Array or arguments object expected
+15074,Invalid operand to '%s': Object expected
+15075,Invalid operand to '%s': Function expected
+15076,Invalid descriptor for property '%s'
+15077,Cannot define property '%s': object is not extensible
+15078,Cannot redefine non-configurable property '%s'
+15079,Cannot modify non-writable property '%s'
+15080,Cannot modify property '%s': 'length' is not writable
+15081,Cannot define property '%s'
+15088,Required argument %s in DataView method is not specified
+15090,DataView constructor argument %s is invalid
+15091,The function '%s' has an invalid signature and cannot be called
+15092,The property '%s' has an invalid signature and cannot be accessed
+15093,The runtimeclass %s that has Windows.Foundation.IPropertyValue as default interface is not supported as input parameter type
+15094,The object with interface Windows.Foundation.IPropertyValue that has runtimeclass name %s is not supported as out parameter
+15096,%s: 'this' is not an Inspectable Object
+15097,%s: could not convert argument to type 'char'
+15098,%s: could not convert argument to type 'GUID'
+15099,%s: could not convert return value to IInspectable
+15100,Could not convert object to struct: object missing expected property '%s'
+15101,Type '%s' not found
+15102,%s: function called with too few arguments
+15103,%s: type is not constructible
+15104,Could not convert value to PropertyValue: %s not supported by PropertyValue
+15105,Could not convert value to IInspectable: %s not supported by IInspectable
+15108,%s: The Inspectable object 'this' is released and cannot be accessed
+15110,'this' is not of expected type: %s
+15112,%s: an unexpected failure occurred while trying to obtain metadata information
+32812,The specified date is not available in the current locale's calendar
diff --git a/src/sentry/data/error-locale/fi-FI.txt b/src/sentry/data/error-locale/fi-FI.txt
new file mode 100644
index 00000000000000..23357499f6c234
--- /dev/null
+++ b/src/sentry/data/error-locale/fi-FI.txt
@@ -0,0 +1,289 @@
+5,Virheellinen toimintosarjakutsu tai argumentti
+6,Ylivuoto
+7,Muisti ei riitä
+9,Viittaus alueen ulkopuolelle
+10,Taulukko on kiinteä tai tilapäisesti lukittu
+11,Jako nollalla
+13,Tyyppivirhe
+14,Merkkijonon tila ei riitä
+17,Toimintoa ei voi suorittaa
+28,Pinotila ei riitä
+35,Sub- tai Function-toimintosarjaa ei ole määritetty
+48,Virhe ladattaessa DLL-kirjastoa
+51,Sisäinen virhe
+52,Virheellinen tiedostonimi tai numero
+53,Tiedostoa ei löydy
+54,Virheellinen tiedostomuoto
+55,Tiedosto on jo avoinna
+57,Laitteen I/O-virhe
+58,Tiedosto on jo olemassa
+61,Levy on täynnä
+62,Syöte tiedoston lopun jälkeen
+67,Liian monta tiedostoa
+68,Laite ei ole käytettävissä
+70,Ei käyttöoikeutta
+71,Levy ei ole valmiina
+74,Uudelleennimettäessä ei voi vaihtaa asemaa
+75,Polkua tai tiedostoa ei voi käyttää
+76,Polkua ei löydy
+91,Object-tyyppistä muuttujaa tai With-lohkomuuttujaa ei ole asetettu
+92,For-silmukkaa ei ole alustettu
+94,Null-arvon virheellinen käyttö
+322,Tarvittavan tilapäistiedoston luominen ei onnistu
+424,Objekti puuttuu
+429,ActiveX-komponentti ei pysty luomaan objektia
+430,Luokka ei tue automaatiota
+432,Tiedoston tai luokan nimeä ei löytynyt automaatiotoiminnon aikana
+438,Objekti ei tue tätä ominaisuutta tai menetelmää
+440,Automaation virhe
+445,Objekti ei tue tätä toimintoa
+446,Objekti ei tue nimettyjä argumentteja
+447,Objekti ei tue käytössä olevia maakohtaisia asetuksia
+448,Nimettyä argumenttia ei löydy
+449,Argumentti on pakollinen
+450,Argumenttien määrä tai ominaisuusasetus ei kelpaa
+451,Objekti ei ole kokoelma
+453,Määritettyä DLL-funktiota ei löydy
+458,Muuttuja käyttää automaatiotyyppiä, jota JavaScriptissä ei tueta
+462,Etäpalvelinkonetta ei ole tai sitä ei voi käyttää
+501,Ei liitettävissä muuttujaan
+502,Objektiin ei ole turvallista liittää komentokieltä
+503,Objektia ei ole turvallista alustaa
+504,Objektia ei ole turvallista luoda
+507,On tapahtunut poikkeus
+1001,Muisti ei riitä
+1002,Syntaksivirhe
+1003,Tarvitaan ':'
+1004,Tarvitaan ';'
+1005,Tarvitaan '('
+1006,Tarvitaan ')'
+1007,Tarvitaan ']'
+1008,Tarvitaan '{'
+1009,Tarvitaan '}'
+1010,Tarvitaan tunniste
+1011,Tarvitaan '='
+1012,Tarvitaan '/'
+1013,Luku ei kelpaa
+1014,Merkki ei kelpaa
+1015,Päättymätön merkkijonovakio
+1016,Päättymätön kommentti
+1018,'return'-lause funktion ulkopuolella
+1019,'break'-lausetta ei voi käyttää silmukan ulkopuolella
+1020,'continue'-lausetta ei voi käyttää silmukan ulkopuolella
+1023,Tarvitaan heksadesimaalinumero
+1024,Tarvitaan 'while'
+1025,Nimi on määritetty uudelleen
+1026,Nimeä ei löydy
+1027,'switch'-lauseessa voi olla vain yksi 'default'-määre
+1028,Tarvitaan tunniste, merkkijono tai numero
+1029,Tarvitaan '@end'
+1030,Ehdollinen kääntäminen on poistettu käytöstä
+1031,Tarvitaan vakio
+1032,Tarvitaan '@'
+1033,Tarvitaan 'catch'
+1034,Tarvitaan 'var'
+1035,throw-kohdetta pitää seurata lauseke samalla lähderivillä
+1037,with-lausekkeita ei sallita tarkassa tilassa
+1038,Muodollisten parametrien nimien kaksoiskappaleita ei sallita tarkassa tilassa
+1039,Numeerisia oktaaliliteraaleja ja ohjausmerkkejä ei sallita tarkassa tilassa
+1041,Virheellinen eval-kohteen käyttö tarkassa tilassa
+1042,Virheellinen arguments-kohteen käyttö tarkassa tilassa
+1045,Delete-kutsun tekemistä lausekkeelle ei sallita tarkassa tilassa
+1046,Ominaisuuden useita määrityksiä ei sallita tarkassa tilassa
+1047,Tarkassa tilassa funktioiden kuvaukset eivät voi olla lausekkeen tai lohkon sisällä, vaan niitä voidaan määrittää ainoastaan päätasolla tai suoraan funktion runko-osassa.
+1048,Avainsanan käyttäminen tunnuksena ei kelpaa
+1049,Varatun sanan käyttäminen tunnuksena ei kelpaa
+1050,Varatun sanan käyttäminen tunnuksena ei kelpaa. Tunnuksen nimi on varattu tarkassa tilassa.
+1051,Setter-funktioilla on oltava yksi argumentti
+4096,JavaScript-käännösvirhe
+4097,Suorituksenaikainen JavaScript-virhe
+4098,Tuntematon suorituksenaikainen virhe
+5000,Ei liitettävissä 'this'-lauseeseen
+5001,Arvoksi on annettava luku
+5002,Tarvitaan funktio
+5003,Ei liitettävissä funktion tulokseen
+5004,Objektia ei voi indeksoida
+5005,Tarvitaan merkkijono
+5006,Arvoksi on annettava päivämääräobjekti
+5007,Arvoksi on annettava objekti
+5008,Virheellinen vasemmanpuoleinen osa määrityksessä
+5009,Tunnistetta ei ole määritetty
+5010,Arvoksi on annettava totuusarvo
+5011,Vapaan komentosarjan koodia ei voi suorittaa
+5012,Arvoksi on annettava objektin jäsen
+5013,Arvoksi on annettava VBArray-taulukko
+5014,Arvoksi on annettava JScript-objekti
+5015,Arvoksi on annettava laskuriobjekti
+5016,Tarvitaan lausekeobjekti
+5017,Säännöllisessä lausekkeessa on syntaksivirhe
+5018,Odottamaton rajoitin
+5019,Säännölliseen lausekkeeseen kuuluu ']'
+5020,Säännölliseen lausekkeeseen kuuluu ')'
+5021,Merkistön arvoalue ei kelpaa
+5022,Keskeytys havaittu muttei käsitelty
+5023,Funktion prototyyppiobjekti ei kelpaa
+5024,Koodattava URI sisältää merkin, joka ei kelpaa
+5025,URIn purettava koodi ei kelpaa
+5026,Murtolukujen määrä on määritettyjen rajojen ulkopuolella
+5027,Tarkkuus on määritettyjen rajojen ulkopuolella
+5028,Tarvitaan array- tai arguments-objekti
+5029,Taulukon pituuden arvon on oltava äärellinen positiivinen kokonaisluku
+5030,Taulukon pituuden arvoksi on määritettävä äärellinen positiivinen kokonaisluku
+5031,Tarvitaan taulukko-objekti
+5034,Arvoargumentin kehäviittauksia ei tueta
+5035,Korvaava argumentti ei kelpaa
+5038,Argumenttiluettelo on liian suuri otettavaksi käyttöön
+5039,Const-ominaisuuden uudelleenmääritys
+5041,Objektijäsentä ei voi määrittää
+5042,Muuttajaa ei ole määritetty tarkassa tilassa
+5043,Funktion tai argumenttiobjektin caller-ominaisuuden käyttöä ei sallita tarkassa tilassa
+5044,Argumenttiobjektin callee-ominaisuuden käyttöä ei sallita tarkassa tilassa
+5045,Vain luku -ominaisuuksiin määritystä ei sallita tarkassa tilassa
+5046,Ominaisuutta ei voi luoda objektille, jota ei voi laajentaa
+5047,Arvoksi on annettava objekti
+5048,Arvoksi on annettava objekti
+5049,Arvoksi on annettava objekti
+5050,Arvoksi on annettava objekti
+5051,Arvoksi on annettava funktio
+5052,Arvoksi on annettava funktio
+5053,Ominaisuudella ei voi olla sekä käyttömenetelmää että arvoa
+5054,this-kohde on tyhjäarvoinen, tai sitä ei ole määritetty
+5055,Arvoksi on annettava objekti
+5056,Arvoksi on annettava funktio
+5057,Arvoksi on annettava merkkijono
+5058,Arvoksi on annettava totuusarvo
+5059,Arvoksi on annettava päivämäärä
+5060,Arvoksi on annettava luku
+5061,Arvoksi on annettava VBArray
+5062,Arvoksi on annettava JScript-objekti
+5063,Arvoksi on annettava laskuriobjekti
+5064,Arvoksi on annettava RegExp-objekti
+5065,Virheellinen funktion argumentti
+5066,Arvoksi on annettava objekti
+5067,Arvoksi on annettava JScript-objekti
+5068,Arvoksi on annettava funktio
+5069,Arvoksi on annettava VBArray
+5070,Arvoksi on annettava objekti
+5071,Arvoksi on annettava objekti
+5072,Virheellinen length-ominaisuus
+5073,Arvoksi on annettava array- tai arguments-objekti
+5074,Virheellinen operandi
+5075,Virheellinen operandi
+5076,Virheellinen ominaisuuskuvaus
+5077,Ominaisuutta ei voi määrittää: objekti ei ole laajennettavissa
+5078,Ei määritettävissä olevaa ominaisuutta ei voi määrittää uudelleen
+5079,Ei kirjoitettavissa olevaa ominaisuutta ei voi muokata
+5080,Ominaisuutta ei voi muokata: length-kohde ei ole kirjoitettavissa
+5081,Ominaisuutta ei voi määrittää
+5082,Tyyppimääritetyn taulukon konstruktoriargumentti ei kelpaa
+5083,"This" ei ole tyyppimääritetyn taulukon objekti
+5084,Virheellinen siirtymä tai pituus tyyppimääritetyn taulukon luonnissa
+5085,Virheellinen alku- tai loppuarvo tyyppimääritetyn taulukon alitaulukkomenetelmässä
+5086,Virheellinen lähde tyyppimääritetyssä taulukkojoukossa
+5087,"This" ei ole DataView-objekti
+5088,DataView sisältää virheellisiä argumentteja
+5089,DataView-toiminto ylittää määritetyn puskuripituuden
+5090,DataView sisältää virheellisiä argumentteja
+5091,virheellinen funktion allekirjoitus
+5092,virheellinen ominaisuuden allekirjoitus
+5093,virheellinen syöteparametrin laji
+5094,virheellinen tulosparametri
+5095,Funktion arguments-ominaisuuden käyttö ei ole sallittua tarkassa tilassa
+5096,Odotettiin tarkastettavaa objektia
+5097,Argumentin muunnos char-tyypiksi ei onnistunut
+5098,Argumentin muunnos GUID-tyypiksi ei onnistunut
+5099,Odotettiin: IInspectable
+5100,Objektin muunnos struct-rakenteeksi ei onnistunut: odotettu ominaisuus puuttuu objektista
+5101,Tuntematon tyyppi
+5102,Funktiokutsussa ei ole riittävästi argumentteja
+5103,Tyyppi ei ole konstruoitava
+5104,Arvon muuntaminen PropertyValue-arvoksi ei onnistunut: PropertyValue ei tue tyyppiä
+5105,Arvon muuntaminen IInspectable-arvoksi ei onnistunut: IInspectable ei tue tyyppiä
+5106,Päivämääräarvon muuntaminen Windows.Foundation.DateTime-arvoksi ei onnistunut: arvo on kelvollisen alueen ulkopuolella
+5107,Arvon muuntaminen Windows.Foundation.TimeSpan-arvoksi ei onnistunut: arvo on kelvollisen alueen ulkopuolella
+5108,Virheellinen vapautetun tarkastettavan objektin käyttö
+5109,Vapautettua tarkastettavaa objektia ei voi vapauttaa
+5110,this-lause ei ole odotettua tyyppiä
+5111,Taulukolle määritetty pituus ja koko ovat virheelliset
+5112,Odottamaton virhe yritettäessä hankkia metatietotietoja
+5200,Tila on virhe, mutta getResults-funktio ei palauttanut virhettä
+5201,Suoritetulle käsittelijälle välitettiin puuttuva tai virheellinen tilan parametri
+5202,Suoritetulle käsittelijälle välitettiin puuttuva tai virheellinen lähettäjän parametri
+6000,Ääretön
+6001,-Ääretön
+10438,Objekti ei tue ominaisuutta tai menetelmää %s
+10449,Funktion %s argumentti on pakollinen
+15001,%s ei ole luku
+15002,%s ei ole funktio
+15004,%s ei ole indeksoitavissa oleva objekti
+15005,%s ei ole merkkijono
+15006,%s ei ole päivämääräobjekti
+15007,%s on tyhjäarvoinen, tai se ei ole objekti
+15008,Kohteeseen %s ei voi määrittää
+15009,%s ei ole määritetty
+15010,%s ei ole totuusarvo
+15012,Kohdetta %s ei voi poistaa
+15013,%s ei ole VBArray-kohde
+15014,%s ei ole JavaScript-objekti
+15015,%s ei ole luettelointiobjekti
+15016,%s ei ole säännöllisen lausekkeen objekti
+15028,%s ei ole Array- tai arguments-objekti
+15031,%s ei ole Array-objekti
+15036,Ominaisuuskuvaimen määritteen %s arvoksi ei voi määrittää true tälle objektille
+15037,Ominaisuuskuvaimen määritteen %s arvoksi ei voi määrittää false tälle objektille
+15039,Const-kohteen %s uudelleenmääritys
+15041,Poistokutsun tekemistä kohteelle %s ei sallita tarkassa tilassa
+15047,Määrittämättömän tai tyhjän viittauksen ominaisuutta %s ei voi määrittää
+15048,Määrittämättömän tai tyhjän viittauksen ominaisuutta %s ei voi noutaa
+15049,Määrittämättömän tai tyhjän viittauksen ominaisuutta %s ei voi poistaa
+15050,Ominaisuutta %s ei voi käyttää: tyyppi VarDate ei tue käyttäjän määrittämiä ominaisuuksia
+15051,Ominaisuuden %s arvo ei ole Function-objekti
+15052,Ominaisuuden %s arvo ei ole Function-objekti, vaan se on tyhjäarvoinen, tai sitä ei ole määritetty
+15054,%s: this-kohde on tyhjäarvoinen, tai sitä ei ole määritetty
+15055,%s: this-kohde ei ole objekti
+15056,%s: this-kohde ei ole Function-objekti
+15057,%s: this-kohde ei ole String-objekti
+15058,%s: this-kohde ei ole Boolean-objekti
+15059,%s: this-kohde ei ole Date-objekti
+15060,%s: this-kohde ei ole Number-objekti
+15061,%s: this-kohde ei ole VBArray-objekti
+15062,%s: this-kohde ei ole JavaScript-objekti
+15063,%s: this-kohde ei ole Enumerator-objekti
+15064,%s: this-kohde ei ole RegExp-objekti
+15065,%s: virheellinen argumentti
+15066,%s: argumentti ei ole objekti
+15067,%s: argumentti ei ole JavaScript-objekti
+15068,%s: argumentti ei ole Function-objekti
+15069,%s: argumentti ei ole VBArray-objekti
+15070,%s: argumentti on tyhjäarvoinen, tai sitä ei ole määritetty
+15071,%s: argumentti ei ole objekti, eikä se ole tyhjäarvoinen
+15072,%s: argumentilla ei ole kelvollista length-ominaisuutta
+15073,%s: arvoksi on annettava Array- tai arguments-objekti
+15074,Virheellinen operandi kohteelle %s: odotetaan objektia
+15075,Virheellinen operandi kohteelle %s: odotetaan funktiota
+15076,Virheellinen kuvain ominaisuudelle %s
+15077,Ominaisuutta %s ei voi määrittää: objekti ei ole laajennettavissa
+15078,Ei määritettävissä olevaa ominaisuutta %s ei voi määrittää uudelleen
+15079,Ei kirjoitettavissa olevaa ominaisuutta %s ei voi muokata
+15080,Ominaisuutta %s ei voi muokata: length-kohde ei ole kirjoitettavissa
+15081,Ominaisuutta %s ei voi määrittää
+15088,DataView-menetelmän pakollista argumenttia %s ei ole määritetty
+15090,DataView-konstruktorin argumentti %s on virheellinen
+15091,Toiminnon %s allekirjoitus on virheellinen. Toimintoa ei voi kutsua
+15092,Ominaisuuden %s allekirjoitus on virheellinen. Ominaisuutta ei voi käyttää
+15093,Ajonaikainen luokka %s, jonka oletusliittymäksi on määritetty Windows.Foundation.IPropertyValue, ei ole tuettu syöteparametrilajina
+15094,Objekti, jonka liittymäksi on määritetty Windows.Foundation.IPropertyValue ja jonka ajonaikainen luokkanimi on %s, ei ole tuettu tulosparametrina
+15096,%s: "this" ei ole tarkastettava objekti
+15097,%s: argumentin muuntaminen char-tyypiksi ei onnistunut
+15098,%s: argumentin muuntaminen GUID-tyypiksi ei onnistunut
+15099,%s: paluuarvon muuntaminen IInspectable-arvoksi ei onnistunut
+15100,Objektin muuntaminen struct-rakenteeksi ei onnistunut: objektista puuttuu odotettu ominaisuus %s
+15101,Tyyppiä %s ei löytynyt
+15102,%s: funktiokutsussa on liian vähän argumentteja
+15103,%s: tyyppi ei ole konstruoitava
+15104,Arvon muunnos PropertyValue-arvoksi epäonnistui: %s ei ole tuettu PropertyValue-arvo
+15105,Arvon muunnos IInspectable-arvoksi epäonnistui: %s ei ole tuettu IInspectable-arvo
+15108,%s: tarkastettava objekti "this" on vapautettu, eikä objektia voi käyttää
+15110,this-lause ei ole odotettua tyyppiä: %s
+15112,%s: odottamaton virhe yritettäessä hankkia metatietotietoja
+32812,Määritettyä päivämäärää ei ole käytössä olevassa maakohtaisessa kalenterissa.
diff --git a/src/sentry/data/error-locale/fr-FR.txt b/src/sentry/data/error-locale/fr-FR.txt
new file mode 100644
index 00000000000000..dfaede480f1f2d
--- /dev/null
+++ b/src/sentry/data/error-locale/fr-FR.txt
@@ -0,0 +1,289 @@
+5,Argument ou appel de procédure incorrect
+6,Dépassement de capacité
+7,Mémoire insuffisante
+9,Indice en dehors de la plage
+10,Tableau fixe ou temporairement verrouillé
+11,Division par zéro
+13,Type incompatible
+14,Espace de chaîne insuffisant
+17,Impossible d'exécuter l'opération requise
+28,Espace pile insuffisant
+35,Sub ou Function non définie
+48,Erreur de chargement de la DLL
+51,Erreur interne
+52,Nom ou numéro de fichier incorrect
+53,Fichier introuvable
+54,Mode fichier incorrect
+55,Fichier déjà ouvert
+57,Erreur d'entrée/sortie de périphérique
+58,Ce fichier existe déjà
+61,Disque plein
+62,L'entrée dépasse la fin du fichier
+67,Trop de fichiers
+68,Périphérique non disponible
+70,Permission refusée
+71,Disque non prêt
+74,Impossible d'attribuer un nom de lecteur différent
+75,Chemin d'accès erroné
+76,Chemin d'accès introuvable
+91,Variable objet ou variable de bloc With non définie
+92,Boucle For non initialisée
+94,Utilisation non autorisée de Null
+322,Impossible de créer le fichier temporaire nécessaire
+424,Objet requis
+429,Un composant ActiveX ne peut pas créer un objet
+430,Cette classe ne gère pas Automation
+432,Nom du fichier ou de la classe introuvable lors de l'opération Automation
+438,Cet objet ne gère pas cette propriété ou cette méthode
+440,Erreur Automation
+445,Cet objet ne gère pas cette action
+446,Cet objet ne gère pas les arguments nommés
+447,Cet objet ne gère pas les paramètres régionaux en cours
+448,Argument nommé introuvable
+449,Argument obligatoire
+450,Nombre d'arguments ou affectation de propriété incorrects
+451,Cet objet n'est pas une collection
+453,Fonction de DLL spécifiée introuvable
+458,Cette variable utilise un type Automation non géré par JavaScript
+462,Le serveur distant n'existe pas ou n'est pas disponible
+501,Affectation à la variable impossible
+502,Objet non sécurisé pour le script
+503,Objet non sécurisé pour l'initialisation
+504,Objet non sécurisé pour la création
+507,Une exception est survenue
+1001,Mémoire insuffisante
+1002,Erreur de syntaxe
+1003,':' attendu
+1004,';' attendu
+1005,'(' attendu
+1006,')' attendu
+1007,']' attendu
+1008,'{' attendu
+1009,'}' attendu
+1010,Identificateur attendu
+1011,'=' attendu
+1012,'/' attendu
+1013,Nombre incorrect
+1014,Caractère incorrect
+1015,Constante chaîne non terminée
+1016,Commentaire non terminé
+1018,Instruction 'return' en dehors d'une fonction
+1019,Un 'break' doit se trouver à l'intérieur d'une boucle
+1020,Un 'continue' doit se trouver à l'intérieur d'une boucle
+1023,Caractère hexadécimal attendu
+1024,'while' attendu
+1025,Étiquette redéfinie
+1026,Étiquette introuvable
+1027,'default' ne peut apparaître qu'une fois dans une instruction 'switch'
+1028,Identificateur, chaîne ou nombre attendu
+1029,'@end' attendu
+1030,La compilation conditionnelle est désactivée
+1031,Constante attendue
+1032,'@' attendu
+1033,'catch' attendu
+1034,'var' attendu
+1035,« throw » doit être suivi par une expression sur la même ligne source
+1037,les instructions « with » ne sont pas autorisées en mode strict
+1038,La duplication de noms de paramètre formels n’est pas autorisée en mode strict
+1039,Les littéraux numériques octaux et les caractères d’échappement ne sont pas autorisés en mode strict
+1041,Utilisation non valide de l’« évaluation » en mode strict
+1042,Utilisation non valide d’« arguments » en mode strict
+1045,Appel de la fonction delete non autorisé en mode strict
+1046,Plusieurs définitions d’une propriété ne sont pas autorisées en mode strict
+1047,En mode strict, les déclarations de fonction ne peuvent pas être imbriquées dans une instruction ou un bloc. Elles peuvent uniquement apparaître au niveau supérieur ou directement dans le corps d’une fonction.
+1048,L’utilisation d’un mot clé pour un identificateur n’est pas valide
+1049,L’utilisation d’un mot réservé futur pour un identificateur n’est pas valide
+1050,L’utilisation d’un mot réservé futur pour un identificateur n’est pas valide. Le nom d’identificateur est réservé en mode strict.
+1051,Les fonctions setter doivent avoir un argument
+4096,Erreur de compilation JavaScript
+4097,Erreur d’exécution JavaScript
+4098,Erreur d'exécution inconnue
+5000,Impossible d'affecter à 'this'
+5001,Nombre attendu
+5002,Fonction attendue
+5003,Impossible d'affecter à un résultat de fonction
+5004,Impossible d'indexer l'objet
+5005,Chaîne attendue
+5006,Objet date attendu
+5007,Objet attendu
+5008,Côté gauche non valide dans l’affectation
+5009,Identificateur non défini
+5010,Booléen attendu
+5011,Impossible d'exécuter le code à partir d'un script libéré
+5012,Membre d'objet attendu
+5013,VBArray attendu
+5014,Objet JavaScript attendu
+5015,Objet d'énumération attendu
+5016,Objet d'expression régulière attendu
+5017,Erreur de syntaxe dans l'expression régulière
+5018,Quantificateur inattendu
+5019,']' attendu dans l'expression régulière
+5020,')' attendu dans l'expression régulière
+5021,Plage incorrecte dans le jeu de caractères
+5022,Exception levée mais non décelée
+5023,La fonction ne possède pas d'objet prototype valide
+5024,L'URI à coder contient un caractère incorrect
+5025,L'URI à décoder contient un caractère incorrect
+5026,Le nombre de fractions est en dehors de la plage
+5027,La précision est en dehors de la plage
+5028,Objet Array ou Arguments attendu
+5029,La longueur du tableau doit être un entier positif fini
+5030,Un nombre positif fini doit être attribué à la longueur du tableau
+5031,Objet Array attendu
+5034,Référence circulaire dans l'argument de valeur non prise en charge
+5035,Argument de remplacement incorrect
+5038,Liste d’arguments trop importante pour être appliquée
+5039,Redéclaration d’une propriété const
+5041,Membre d’objet non configurable
+5042,Variable non définie en mode strict
+5043,Accès à la propriété « caller » d’une fonction ou d’un objet arguments non autorisé en mode strict
+5044,Accès à la propriété « callee » d’un objet arguments non autorisé en mode strict
+5045,Affectation aux propriétés en lecture seule non autorisée en mode strict
+5046,Impossible de créer une propriété pour un objet non extensible
+5047,Objet attendu
+5048,Objet attendu
+5049,Objet attendu
+5050,Objet attendu
+5051,Fonction attendue
+5052,Fonction attendue
+5053,Une propriété ne peut pas avoir des accesseurs et une valeur
+5054,'this' est null ou non défini
+5055,Objet attendu
+5056,Fonction attendue
+5057,Chaîne attendue
+5058,Booléen attendu
+5059,Date attendue
+5060,Nombre attendu
+5061,VBArray attendu
+5062,Objet JavaScript attendu
+5063,Objet d’énumération attendu
+5064,Objet RegExp attendu
+5065,Argument de fonction non valide
+5066,Objet attendu
+5067,Objet JavaScript attendu
+5068,Fonction attendue
+5069,VBArray attendu
+5070,Objet attendu
+5071,Objet attendu
+5072,Propriété de « longueur » non valide
+5073,Objet Array ou Arguments attendu
+5074,Opérande non valide
+5075,Opérande non valide
+5076,Descripteur de propriété non valide
+5077,Impossible de définir une propriété : objet non extensible
+5078,Impossible de redéfinir une propriété non configurable
+5079,Impossible de modifier une propriété non accessible en écriture
+5080,Impossible de modifier une propriété : « length » n’est pas accessible en écriture
+5081,Impossible de définir une propriété
+5082,L’argument du constructeur de tableau typé n’est pas valide
+5083,« this » n’est pas un objet de tableau typé
+5084,Décalage/longueur non valide lors de la création du tableau typé
+5085,Valeur de début/fin non valide dans la méthode sous-tableau du tableau typé
+5086,Source non valide dans l’ensemble de tableaux typés
+5087,« this » n’est pas un objet DataView
+5088,Arguments non valides dans DataView
+5089,Accès à l’opération DataView au-delà de la longueur de tampon spécifiée
+5090,Arguments non valides dans DataView
+5091,Signature de fonction non valide
+5092,Signature de propriété non valide
+5093,type de paramètre d’entrée non valide
+5094,paramètre de sortie non valide
+5095,Accès à la propriété « arguments » d’une fonction non autorisé en mode strict
+5096,Objet Inspectable attendu
+5097,Impossible de convertir l’argument en type « char »
+5098,Impossible de convertir l’argument en type « GUID »
+5099,IInspectable attendu
+5100,Impossible de convertir l’objet en struct : propriété attendue manquante pour l’objet
+5101,Type inconnu
+5102,Fonction appelée avec trop peu d’arguments
+5103,Type non constructible
+5104,Impossible de convertir la valeur en PropertyValue : type non pris en charge par PropertyValue
+5105,Impossible de convertir la valeur en IInspectable : type non pris en charge par IInspectable
+5106,Impossible de convertir Date en Windows.Foundation.DateTime : valeur située en dehors de la plage valide
+5107,Impossible de convertir la valeur en Windows.Foundation.TimeSpan : valeur située en dehors de la plage valide
+5108,Accès non valide à un objet Inspectable déjà libéré
+5109,Impossible de libérer un objet Inspectable déjà libéré
+5110,« this » n’est pas du type attendu
+5111,Longueur et taille non autorisées spécifiées pour le tableau
+5112,Une défaillance inattendue s’est produite lors de la tentative d’obtention d’informations de métadonnées.
+5200,L’état a la valeur Erreur, mais getResults n’a pas retourné d’erreur.
+5201,Paramètre d’état manquant ou non valide passé au gestionnaire completed
+5202,Paramètre d’expéditeur manquant ou non valide passé au gestionnaire completed
+6000,Infini
+6001,-Infini
+10438,L’objet ne gère pas la propriété ou la méthode « %s »
+10449,L’argument pour la fonction « %s » n’est pas facultatif
+15001,« %s » n’est pas un nombre
+15002,« %s » n’est pas une fonction
+15004,« %s » n’est pas un objet indexable
+15005,« %s » n’est pas une chaîne
+15006,« %s » n’est pas un objet de date
+15007,« %s » a la valeur null ou n’est pas un objet
+15008,Impossible d’affecter à « %s »
+15009,« %s » est indéfini
+15010,« %s » n’est pas un opérateur booléen
+15012,Impossible de supprimer « %s »
+15013,« %s » n’est pas un VBArray
+15014,« %s » n’est pas un objet JavaScript
+15015,« %s » n’est pas un objet d’énumération
+15016,« %s » n’est pas un objet d’expression régulière
+15028,%s n’est pas un objet Array ou Arguments
+15031,%s n’est pas un objet Array
+15036,L’attribut « %s » du descripteur de propriété ne peut pas être défini à « true » sur cet objet
+15037,L’attribut « %s » du descripteur de propriété ne peut pas avoir la valeur « false » sur cet objet
+15039,Redéclaration de const « %s »
+15041,Appel de la fonction delete sur « %s » non autorisé en mode strict
+15047,Impossible de définir la propriété « %s » d’une référence null ou non définie
+15048,Impossible d’obtenir la propriété « %s » d’une référence null ou non définie
+15049,Impossible de supprimer la propriété « %s » d’une référence null ou non définie
+15050,Impossible d’accéder à la propriété « %s » : le type « VarDate » ne prend pas en charge les propriétés définies par l’utilisateur
+15051,La valeur de la propriété « %s » n’est pas un objet Function
+15052,La valeur de la propriété « %s » est null ou non définie, pas un objet Function
+15054,%s : 'this' est null ou non défini
+15055,%s : 'this' n’est pas un objet Object
+15056,%s : 'this' n’est pas un objet Function
+15057,%s : 'this' n’est pas un objet String
+15058,%s : 'this' n’est pas un objet booléen
+15059,%s : 'this' n’est pas un objet Date
+15060,%s : 'this' n’est pas un objet Number
+15061,%s : 'this' n’est pas un objet VBArray
+15062,%s : 'this' n’est pas un objet JavaScript
+15063,%s : 'this' n’est pas un objet Enumerator
+15064,%s : 'this' n’est pas un objet RegExp
+15065,%s : argument non valide
+15066,%s : l’argument n’est pas un objet Object
+15067,%s : l’argument n’est pas un objet JavaScript
+15068,%s : l’argument n’est pas un objet Function
+15069,%s : l’argument n’est pas un objet VBArray
+15070,%s : l’argument est null ou non défini
+15071,%s : l’argument n’est pas un objet Object et est non null
+15072,%s : l’argument n’a pas une propriété de « longueur » valide
+15073,%s : Objet Array ou Arguments attendu
+15074,Opérande non valide pour « %s » : objet Object attendu
+15075,Opérande non valide pour « %s » : objet Function attendu
+15076,Descripteur non valide pour la propriété « %s »
+15077,Impossible de définir une propriété « %s » : objet non extensible
+15078,Impossible de redéfinir une propriété non configurable « %s »
+15079,Impossible de modifier une propriété non accessible en écriture « %s »
+15080,Impossible de modifier la propriété « %s » : « length » n’est pas accessible en écriture
+15081,Impossible de définir une propriété « %s »
+15088,L’argument obligatoire %s dans la méthode DataView n’est pas spécifié
+15090,L’argument %s du constructeur DataView n’est pas valide
+15091,La signature de la fonction « %s » n’est pas valide et ne peut pas être appelée
+15092,La signature de la propriété « %s » n’est pas valide et n’est pas accessible
+15093,La classe runtime %s avec Windows.Foundation.IPropertyValue comme interface par défaut n’est pas prise en charge en tant que type de paramètre d’entrée
+15094,L’objet avec l’interface Windows.Foundation.IPropertyValue avec le nom runtimeclass %s n’est pas pris en charge en tant que paramètre de sortie
+15096,%s : « this » n’est pas un objet Inspectable
+15097,%s : impossible de convertir l’argument en type « char »
+15098,%s : impossible de convertir l’argument en type « GUID »
+15099,%s : impossible de convertir la valeur renvoyée en IInspectable
+15100,Impossible de convertir l’objet en struct : propriété attendue « %s » manquante pour l’objet
+15101,Type « %s » introuvable
+15102,%s : fonction appelée avec trop peu d’arguments
+15103,%s : type non constructible
+15104,Impossible de convertir la valeur en PropertyValue : %s non pris en charge par PropertyValue
+15105,Impossible de convertir la valeur en IInspectable : %s non pris en charge par IInspectable
+15108,%s : l’objet Inspectable « this » est libéré et inaccessible.
+15110,« this » n’est pas du type attendu : %s
+15112,%s : une défaillance inattendue s’est produite lors de la tentative d’obtention d’informations de métadonnées.
+32812,La date spécifiée n'est pas disponible dans le calendrier régional actuel
diff --git a/src/sentry/data/error-locale/he-IL.txt b/src/sentry/data/error-locale/he-IL.txt
new file mode 100644
index 00000000000000..9442cb329fb12d
--- /dev/null
+++ b/src/sentry/data/error-locale/he-IL.txt
@@ -0,0 +1,289 @@
+5,ארגומנט או קריאה לפרוצדורה לא חוקיים
+6,גלישה
+7,אין זיכרון פנוי
+9,Subscript מחוץ לטווח
+10,מערך זה קבוע או נעול באופן זמני
+11,חלוקה באפס
+13,אי-התאמה של סוג
+14,אין שטח מחרוזת פנוי
+17,אין אפשרות לבצע פעולה מבוקשת
+28,אין שטח מחסנית פנוי
+35,שיגרת משנה או פונקציה לא הוגדרו
+48,שגיאה בטעינת DLL
+51,שגיאה פנימית
+52,מספר או שם קובץ שגויים
+53,הקובץ לא נמצא
+54,מצב קובץ שגוי
+55,הקובץ פתוח כבר
+57,שגיאת קלט/פלט בהתקן
+58,הקובץ קיים כבר
+61,הדיסק מלא
+62,הקלט הוזן מעבר לסוף הקובץ
+67,קבצים רבים מדי
+68,התקן לא זמין
+70,ההרשאה נדחתה
+71,הדיסק אינו מוכן
+74,אין אפשרות לשנות שם באמצעות כונן אחר
+75,שגיאת גישה לנתיב/קובץ
+76,הנתיב לא נמצא
+91,המשתנה Object או המשתנה With block לא הוגדרו
+92,לולאה מסוג For לא אותחלה
+94,שימוש לא חוקי בערך Null
+322,אין אפשרות ליצור קובץ זמני נחוץ
+424,דרוש אובייקט
+429,לשרת אוטומציה אין אפשרות ליצור אובייקט
+430,המחלקה אינה תומכת באוטומציה
+432,שם קובץ או שם מחלקה לא נמצאו במהלך פעולת אוטומציה
+438,האובייקט אינו תומך במאפיין או בפעולת שירות אלה
+440,שגיאת אוטומציה
+445,האובייקט אינו תומך בפעולה זו
+446,האובייקט אינו תומך בארגומנטים בעלי שם
+447,האובייקט אינו תומך בהגדרת האזור הנוכחית
+448,ארגומנט בעל שם לא נמצא
+449,הארגומנט אינו אופציונלי
+450,מספר ארגומנטים שגוי או הקצאת מאפיין לא חוקית
+451,האובייקט אינו אוסף
+453,פונקציית DLL שצוינה לא נמצאה
+458,המשתנה עושה שימוש בסוג אוטומציה שאינו נתמך ב- JavaScript
+462,מחשב השרת המרוחק אינו קיים או אינו זמין
+501,אין אפשרות להקצות למשתנה
+502,האובייקט אינו בטוח עבור Scripting
+503,האובייקט אינו בטוח לאתחול
+504,האובייקט אינו בטוח ליצירה
+507,אירע מצב חריג
+1001,אין זיכרון פנוי
+1002,שגיאת תחביר
+1003,נדרש ':'
+1004,נדרש ';'
+1005,נדרש ')'
+1006,נדרש '('
+1007,נדרש '['
+1008,נדרש '}'
+1009,נדרש '{'
+1010,נדרש מזהה
+1011,נדרש '='
+1012,נדרש '/'
+1013,מספר לא חוקי
+1014,תו לא חוקי
+1015,קבוע מחרוזת לא גמור
+1016,הערה לא גמורה
+1018,משפט 'return' מחוץ לפונקציה
+1019,אין אפשרות לקיום 'break' מחוץ ללולאה
+1020,אין אפשרות לקיום 'continue' מחוץ ללולאה
+1023,נדרשת ספרה הקסדצימאלית
+1024,נדרש 'while'
+1025,התווית הוגדרה מחדש
+1026,התווית לא נמצאה
+1027,הערך 'default' יכול להופיע פעם אחת בלבד במשפט 'switch'
+1028,נדרשים מזהה, מחרוזת או מספר
+1029,נדרש '@end'
+1030,קומפילציה מותנית אינה פועלת
+1031,נדרש קבוע
+1032,נדרש '@'
+1033,נדרש 'catch'
+1034,נדרש 'var'
+1035,לאחר 'throw' צריך להופיע ביטוי באותה שורת מקור
+1037,משפטי 'with' אינם מותרים במצב קפדני
+1038,שמות פרמטרים רשמיים כפולים אינם מותרים במצב קפדני
+1039,ליטרלים מספריים אוקטליים ותווי ביטול אינם מותרים במצב קפדני
+1041,שימוש לא חוקי ב- 'eval' במצב קפדני
+1042,שימוש לא חוקי ב- 'arguments' במצב קפדני
+1045,קריאה למחיקה בביטוי אינה מותרת במצב קפדני
+1046,הגדרות מרובות של מאפיין אינן מותרות במצב קפדני
+1047,במצב קפדני, לא ניתן לקנן הצהרות פונקציה בתוך משפט או בלוק. הן יכולות להופיע רק ברמה העליונה או ישירות בתוך גוף פונקציה.
+1048,השימוש במילת מפתח עבור מזהה אינו חוקי
+1049,השימוש במילה שמורה עתידית עבור מזהה אינו חוקי
+1050,השימוש במילה שמורה עתידית עבור מזהה אינו חוקי. שם המזהה שמור במצב קפדני.
+1051,פונקציות רכיב מגדיר חייבות לכלול ארגומנט אחד
+4096,שגיאת קומפילציה של JavaScript
+4097,שגיאת זמן ריצה של JavaScript
+4098,שגיאת זמן ריצה לא ידועה
+5000,אין אפשרות להקצות עבור 'this'
+5001,נדרש מספר
+5002,נדרשת פונקציה
+5003,אין אפשרות להקצות לתוצאת פונקציה
+5004,אין אפשרות ליצור אינדקס עבור האובייקט
+5005,נדרשת מחרוזת
+5006,נדרש אובייקט תאריך
+5007,נדרש אובייקט
+5008,צד שמאלי לא חוקי בהקצאה
+5009,מזהה לא מוגדר
+5010,נדרש ערך בוליאני
+5011,אין אפשרות להפעיל קוד מתוך Script פנוי
+5012,נדרש פריט אובייקט
+5013,נדרש VBArray
+5014,נדרש אובייקט JavaScript
+5015,נדרש אובייקט מונה
+5016,נדרש אובייקט ביטוי רגיל
+5017,שגיאת תחביר בביטוי רגיל
+5018,מכמת לא צפוי
+5019,נדרש '[' בביטוי רגיל
+5020,נדרש '(' בביטוי רגיל
+5021,טווח לא חוקי בערכת תווים
+5022,חריג התרחש ולא נתפס
+5023,לפונקציה אין אובייקט אב-טיפוס חוקי
+5024,ה- URI המיועד לקידוד מכיל תו לא חוקי
+5025,ה- URI המיועד לפענוח אינו קידוד חוקי
+5026,מספר ספרות השברים נמצא מחוץ לטווח
+5027,הדיוק נמצא מחוץ לטווח
+5028,נדרש אובייקט מערך או ארגומנטים
+5029,אורך המערך חייב להיות מספר שלם חיובי סופי
+5030,יש להקצות לאורך המערך מספר חיובי סופי
+5031,נדרש אובייקט מערך
+5034,הפניה מעגלית בארגומנט ערך אינה נתמכת
+5035,ארגומנט מחליף לא חוקי
+5038,רשימת הארגומנטים גדולה מכדי שניתן יהיה להחילה
+5039,הצהרה מחדש על מאפיין קבוע
+5041,פריט אובייקט אינו ניתן להגדרה
+5042,משתנה לא מוגדר במצב קפדני
+5043,גישה למאפיין 'caller' של אובייקט פונקציה או ארגומנטים אסורה במצב קפדני
+5044,גישה למאפיין 'callee' של אובייקט ארגומנטים אסורה במצב קפדני
+5045,הקצאה למאפיינים לקריאה בלבד אסורה במצב קפדני
+5046,אין אפשרות ליצור מאפיין עבור אובייקט שאינו ניתן להרחבה
+5047,נדרש אובייקט
+5048,נדרש אובייקט
+5049,נדרש אובייקט
+5050,נדרש אובייקט
+5051,נדרשת פונקציה
+5052,נדרשת פונקציה
+5053,מאפיין אינו יכול להכיל גם רכיבי גישה (Accessor) וגם ערך
+5054,'this' הוא Null או לא מוגדר
+5055,נדרש אובייקט
+5056,נדרשת פונקציה
+5057,נדרשת מחרוזת
+5058,נדרש ערך בוליאני
+5059,נדרש תאריך
+5060,נדרש מספר
+5061,נדרש VBArray
+5062,נדרש אובייקט JavaScript
+5063,נדרש אובייקט מונה
+5064,נדרש אובייקט RegExp
+5065,ארגומנט פונקציה לא חוקי
+5066,נדרש אובייקט
+5067,נדרש אובייקט JavaScript
+5068,נדרשת פונקציה
+5069,נדרש VBArray
+5070,נדרש אובייקט
+5071,נדרש אובייקט
+5072,מאפיין 'length' לא חוקי
+5073,נדרש אובייקט מערך או ארגומנטים
+5074,אופרנד לא חוקי
+5075,אופרנד לא חוקי
+5076,מתאר מאפיין לא חוקי
+5077,לא ניתן להגדיר מאפיין: האובייקט אינו ניתן להרחבה
+5078,לא ניתן להגדיר מחדש מאפיין שאין אפשרות לקבוע את תצורתו
+5079,לא ניתן לשנות מאפיין שאינו ניתן לכתיבה
+5080,לא ניתן לשנות מאפיין: 'length' אינו ניתן לכתיבה
+5081,לא ניתן להגדיר מאפיין
+5082,ארגומנט הבנאי של המערך המסווג אינו חוקי
+5083,'this' אינו אובייקט מערך מסווג
+5084,היסט/אורך לא חוקיים בעת יצירת מערך מסווג
+5085,ערך התחלה/סיום לא חוקי בפעולת שירות של מערך משנה של מערך מסווג
+5086,מקור לא חוקי בקבוצת מערך מסווג
+5087,'this' אינו אובייקט DataView
+5088,ארגומנטים לא חוקיים ב- DataView
+5089,גישה לפעולת DataView מעבר לאורך המאגר שצוין
+5090,ארגומנטים לא חוקיים ב- DataView
+5091,חתימת פונקציה לא חוקית
+5092,חתימת מאפיין לא חוקית
+5093,סוג פרמטר קלט לא חוקי
+5094,פרמטר פלט לא חוקי
+5095,גישה למאפיין 'arguments' של פונקציה אסורה במצב קפדני
+5096,המערכת ציפתה לאובייקט הניתן לבדיקה
+5097,לא היתה אפשרות להמיר ארגומנט לסוג 'char'
+5098,לא היתה אפשרות להמיר ארגומנט לסוג 'GUID'
+5099,המערכת ציפתה ל- IInspectable
+5100,לא היתה אפשרות להמיר אובייקט למבנה: באובייקט חסר מאפיין צפוי
+5101,סוג לא ידוע
+5102,בוצעה קריאה לפונקציה עם מעט מדי ארגומנטים
+5103,הסוג אינו ניתן לבנייה
+5104,לא היתה אפשרות להמיר ערך ל- PropertyValue: הסוג אינו נתמך על-ידי PropertyValue
+5105,לא היתה אפשרות להמיר ערך ל- IInspectable: הסוג אינו נתמך על-ידי IInspectable
+5106,לא היתה אפשרות להמיר תאריך ל- Windows.Foundation.DateTime: הערך נמצא מחוץ לטווח החוקי
+5107,לא היתה אפשרות להמיר ערך ל- Windows.Foundation.TimeSpan: הערך נמצא מחוץ לטווח החוקי
+5108,גישה לא חוקית לאובייקט הניתן לבדיקה ששוחרר כבר
+5109,אין אפשרות לשחרר אובייקט הניתן לבדיקה ששוחרר כבר
+5110,'this' אינו מהסוג הצפוי
+5111,צוינו אורך וגודל לא חוקיים עבור המערך
+5112,כשל לא צפוי אירע בעת ניסיון להשיג מידע של מטה-נתונים
+5200,המצב הוא 'error', אך getResults לא החזיר שגיאה
+5201,פרמטר מצב חסר או לא חוקי הועבר למטפל שהושלם
+5202,פרמטר שולח חסר או לא חוקי הועבר למטפל שהושלם
+6000,אינסוף
+6001,-אינסוף
+10438,Object doesn't support property or method '%s'
+10449,ארגומנט לפונקציה '%s' אינו אופציונלי
+15001,'%s' אינו מספר
+15002,'%s' אינו פונקציה
+15004,'%s' אינו אובייקט שניתן ליצור עבורו אינדקס
+15005,'%s' אינו מחרוזת
+15006,'%s' אינו אובייקט תאריך
+15007,'%s' ריק או שאינו אובייקט
+15008,אין אפשרות להקצות עבור '%s'
+15009,'%s' אינו מוגדר
+15010,'%s' אינו ערך בוליאני
+15012,אין אפשרות למחוק את '%s'
+15013,'%s' אינו VBArray
+15014,'%s' אינו אובייקט JavaScript
+15015,'%s' אינו אובייקט מונה
+15016,'%s' אינו אובייקט ביטוי רגיל
+15028,%s אינו אובייקט מערך או ארגומנטים
+15031,%s אינו אובייקט מערך
+15036,לא ניתן להגדיר את התכונה '%s' במתאר המאפיין לערך 'true' באובייקט זה
+15037,לא ניתן להגדיר את התכונה '%s' במתאר המאפיין לערך 'false' באובייקט זה
+15039,הצהרה מחדש על הקבוע '%s'
+15041,קריאה למחיקה ב- '%s' אסורה במצב קפדני
+15047,לא ניתן לקבוע את המאפיין '%s' של הפניה לא מוגדרת או Null
+15048,Unable to get property '%s' of undefined or null reference
+15049,לא ניתן למחוק את המאפיין '%s' של הפניה לא מוגדרת או Null
+15050,לא ניתן לגשת למאפיין '%s': הסוג 'VarDate' אינו תומך במאפיינים המוגדרים על-ידי המשתמש
+15051,הערך של המאפיין '%s' אינו אובייקט פונקציה
+15052,הערך של המאפיין '%s' הוא Null או לא מוגדר, ואינו אובייקט פונקציה
+15054,%s: 'this' הוא Null או לא מוגדר
+15055,%s: 'this' אינו אובייקט
+15056,%s: 'this' אינו אובייקט פונקציה
+15057,%s: 'this' אינו אובייקט מחרוזת
+15058,%s: 'this' אינו אובייקט בוליאני
+15059,%s: 'this' אינו אובייקט תאריך
+15060,%s: 'this' אינו אובייקט מספר
+15061,%s: 'this' אינו אובייקט VBArray
+15062,%s: 'this' אינו אובייקט JavaScript
+15063,%s: 'this' אינו אובייקט מונה
+15064,%s: 'this' אינו אובייקט RegExp
+15065,%s: ארגומנט לא חוקי
+15066,%s: ארגומנט אינו אובייקט
+15067,%s: ארגומנט אינו אובייקט JavaScript
+15068,%s: ארגומנט אינו אובייקט פונקציה
+15069,%s: ארגומנט אינו אובייקט VBArray
+15070,%s: ארגומנט Null או לא מוגדר
+15071,%s: ארגומנט אינו אובייקט ואינו Null
+15072,%s: ארגומנט אינו כולל מאפיין 'length' חוקי
+15073,%s: נדרש אובייקט מערך או ארגומנטים
+15074,אופרנד לא חוקי ל- '%s': נדרש אובייקט
+15075,אופרנד לא חוקי ל- '%s': נדרשת פונקציה
+15076,מתאר לא חוקי עבור המאפיין '%s'
+15077,לא ניתן להגדיר מאפיין '%s': האובייקט אינו ניתן להרחבה
+15078,לא ניתן להגדיר מחדש מאפיין שאין אפשרות לקבוע את תצורתו '%s'
+15079,לא ניתן לשנות מאפיין שאינו ניתן לכתיבה '%s'
+15080,לא ניתן לשנות מאפיין '%s': 'length' אינו ניתן לכתיבה
+15081,לא ניתן להגדיר מאפיין '%s'
+15088,הארגומנט הנדרש %s בפעולת שירות של DataView לא צוין
+15090,ארגומנט הבנאי %s של DataView אינו חוקי
+15091,לפונקציה '%s' יש חתימה לא חוקית ולא ניתן לקרוא לה
+15092,למאפיין '%s' יש חתימה לא חוקית ולא ניתן לגשת אליו
+15093,ה- runtimeclass בשם %s שממשק ברירת המחדל שלו הוא Windows.Foundation.IPropertyValue אינו נתמך כסוג פרמטר קלט
+15094,האובייקט בעל הממשק Windows.Foundation.IPropertyValue ששם ה- runtimeclass שלו הוא %s אינו נתמך כפרמטר פלט
+15096,%s: 'this' אינו אובייקט הניתן לבדיקה
+15097,%s: לא היתה אפשרות להמיר ארגומנט לסוג 'char'
+15098,%s: לא היתה אפשרות להמיר ארגומנט לסוג 'GUID'
+15099,%s: לא היתה אפשרות להמיר ערך החזרה ל- IInspectable
+15100,לא היתה אפשרות להמיר אובייקט למבנה: באובייקט חסר מאפיין צפוי '%s'
+15101,הסוג '%s' לא נמצא
+15102,%s: בוצעה קריאה לפונקציה עם מעט מדי ארגומנטים
+15103,%s: הסוג אינו ניתן לבנייה
+15104,לא היתה אפשרות להמיר ערך ל- PropertyValue: %s אינו נתמך על-ידי PropertyValue
+15105,לא היתה אפשרות להמיר ערך ל- IInspectable: %s אינו נתמך על-ידי IInspectable
+15108,%s: האובייקט הניתן לבדיקה 'this' משוחרר ואין אפשרות לגשת אליו
+15110,'this' אינו מהסוג הצפוי: %s
+15112,%s: כשל לא צפוי אירע בעת ניסיון להשיג מידע של מטה-נתונים
+32812,התאריך שצוין אינו זמין בלוח השנה של האזור הנוכחי
diff --git a/src/sentry/data/error-locale/hr-HR.txt b/src/sentry/data/error-locale/hr-HR.txt
new file mode 100644
index 00000000000000..6580983d9b4a88
--- /dev/null
+++ b/src/sentry/data/error-locale/hr-HR.txt
@@ -0,0 +1,289 @@
+5,Invalid procedure call or argument
+6,Overflow
+7,Out of memory
+9,Subscript out of range
+10,This array is fixed or temporarily locked
+11,Division by zero
+13,Type mismatch
+14,Out of string space
+17,Can't perform requested operation
+28,Out of stack space
+35,Sub or Function not defined
+48,Error in loading DLL
+51,Internal error
+52,Bad file name or number
+53,File not found
+54,Bad file mode
+55,File already open
+57,Device I/O error
+58,File already exists
+61,Disk full
+62,Input past end of file
+67,Too many files
+68,Device unavailable
+70,Permission denied
+71,Disk not ready
+74,Can't rename with different drive
+75,Path/File access error
+76,Path not found
+91,Object variable or With block variable not set
+92,For loop not initialized
+94,Invalid use of Null
+322,Can't create necessary temporary file
+424,Object required
+429,Automation server can't create object
+430,Class doesn't support Automation
+432,File name or class name not found during Automation operation
+438,Object doesn't support this property or method
+440,Automation error
+445,Object doesn't support this action
+446,Object doesn't support named arguments
+447,Object doesn't support current locale setting
+448,Named argument not found
+449,Argument not optional
+450,Wrong number of arguments or invalid property assignment
+451,Object not a collection
+453,Specified DLL function not found
+458,Variable uses an Automation type not supported in JavaScript
+462,The remote server machine does not exist or is unavailable
+501,Cannot assign to variable
+502,Object not safe for scripting
+503,Object not safe for initializing
+504,Object not safe for creating
+507,An exception occurred
+1001,Out of memory
+1002,Syntax error
+1003,Expected ':'
+1004,Expected ';'
+1005,Expected '('
+1006,Expected ')'
+1007,Expected ']'
+1008,Expected '{'
+1009,Expected '}'
+1010,Expected identifier
+1011,Expected '='
+1012,Expected '/'
+1013,Invalid number
+1014,Invalid character
+1015,Unterminated string constant
+1016,Unterminated comment
+1018,'return' statement outside of function
+1019,Can't have 'break' outside of loop
+1020,Can't have 'continue' outside of loop
+1023,Expected hexadecimal digit
+1024,Expected 'while'
+1025,Label redefined
+1026,Label not found
+1027,'default' can only appear once in a 'switch' statement
+1028,Expected identifier, string or number
+1029,Expected '@end'
+1030,Conditional compilation is turned off
+1031,Expected constant
+1032,Expected '@'
+1033,Expected 'catch'
+1034,Expected 'var'
+1035,'throw' must be followed by an expression on the same source line
+1037,'with' statements are not allowed in strict mode
+1038,Duplicate formal parameter names not allowed in strict mode
+1039,Octal numeric literals and escape characters not allowed in strict mode
+1041,Invalid usage of 'eval' in strict mode
+1042,Invalid usage of 'arguments' in strict mode
+1045,Calling delete on expression not allowed in strict mode
+1046,Multiple definitions of a property not allowed in strict mode
+1047,In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.
+1048,The use of a keyword for an identifier is invalid
+1049,The use of a future reserved word for an identifier is invalid
+1050,The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.
+1051,Setter functions must have one argument
+4096,JavaScript compilation error
+4097,JavaScript runtime error
+4098,Unknown runtime error
+5000,Cannot assign to 'this'
+5001,Number expected
+5002,Function expected
+5003,Cannot assign to a function result
+5004,Cannot index object
+5005,String expected
+5006,Date object expected
+5007,Object expected
+5008,Invalid left-hand side in assignment
+5009,Undefined identifier
+5010,Boolean expected
+5011,Can't execute code from a freed script
+5012,Object member expected
+5013,VBArray expected
+5014,JavaScript object expected
+5015,Enumerator object expected
+5016,Regular Expression object expected
+5017,Syntax error in regular expression
+5018,Unexpected quantifier
+5019,Expected ']' in regular expression
+5020,Expected ')' in regular expression
+5021,Invalid range in character set
+5022,Exception thrown and not caught
+5023,Function does not have a valid prototype object
+5024,The URI to be encoded contains an invalid character
+5025,The URI to be decoded is not a valid encoding
+5026,The number of fractional digits is out of range
+5027,The precision is out of range
+5028,Array or arguments object expected
+5029,Array length must be a finite positive integer
+5030,Array length must be assigned a finite positive number
+5031,Array object expected
+5034,Circular reference in value argument not supported
+5035,Invalid replacer argument
+5038,Argument list too large to apply
+5039,Redeclaration of const property
+5041,Object member not configurable
+5042,Variable undefined in strict mode
+5043,Accessing the 'caller' property of a function or arguments object is not allowed in strict mode
+5044,Accessing the 'callee' property of an arguments object is not allowed in strict mode
+5045,Assignment to read-only properties is not allowed in strict mode
+5046,Cannot create property for a non-extensible object
+5047,Object expected
+5048,Object expected
+5049,Object expected
+5050,Object expected
+5051,Function expected
+5052,Function expected
+5053,Property cannot have both accessors and a value
+5054,'this' is null or undefined
+5055,Object expected
+5056,Function expected
+5057,String expected
+5058,Boolean expected
+5059,Date expected
+5060,Number expected
+5061,VBArray expected
+5062,JavaScript object expected
+5063,Enumerator object expected
+5064,RegExp object expected
+5065,Invalid function argument
+5066,Object expected
+5067,JavaScript object expected
+5068,Function expected
+5069,VBArray expected
+5070,Object expected
+5071,Object expected
+5072,Invalid 'length' property
+5073,Array or arguments object expected
+5074,Invalid Operand
+5075,Invalid Operand
+5076,Invalid property descriptor
+5077,Cannot define property: object is not extensible
+5078,Cannot redefine non-configurable property
+5079,Cannot modify non-writable property
+5080,Cannot modify property: 'length' is not writable
+5081,Cannot define property
+5082,Typed array constructor argument is invalid
+5083,'this' is not a typed array object
+5084,Invalid offset/length when creating typed array
+5085,Invalid begin/end value in typed array subarray method
+5086,Invalid source in typed array set
+5087,'this' is not a DataView object
+5088,Invalid arguments in DataView
+5089,DataView operation access beyond specified buffer length
+5090,Invalid arguments in DataView
+5091,invalid function signature
+5092,invalid property signature
+5093,invalid input parameter type
+5094,invalid ouput parameter
+5095,Accessing the 'arguments' property of a function is not allowed in strict mode
+5096,Inspectable Object expected
+5097,Could not convert argument to type 'char'
+5098,Could not convert argument to type 'GUID'
+5099,IInspectable expected
+5100,Could not convert object to struct: object missing expected property
+5101,Unknown type
+5102,Function called with too few arguments
+5103,Type is not constructible
+5104,Could not convert value to PropertyValue: Type not supported by PropertyValue
+5105,Could not convert value to IInspectable: Type not supported by IInspectable
+5106,Could not convert Date to Windows.Foundation.DateTime: value outside of valid range
+5107,Could not convert value to Windows.Foundation.TimeSpan: value outside of valid range
+5108,Invalid access to already released Inspectable Object
+5109,Cannot release already released Inspectable Object
+5110,'this' is not of the expected type
+5111,Illegal length and size specified for the array
+5112,An unexpected failure occurred while trying to obtain metadata information
+5200,Status is 'error', but getResults did not return an error
+5201,Missing or invalid status parameter passed to completed handler
+5202,Missing or invalid sender parameter passed to completed handler
+6000,Infinity
+6001,-Infinity
+10438,Object doesn't support property or method '%s'
+10449,Argument to the function '%s' is not optional
+15001,'%s' is not a number
+15002,'%s' is not a function
+15004,'%s' is not an indexable object
+15005,'%s' is not a string
+15006,'%s' is not a date object
+15007,'%s' is null or not an object
+15008,Cannot assign to '%s'
+15009,'%s' is undefined
+15010,'%s' is not a boolean
+15012,Cannot delete '%s'
+15013,'%s' is not a VBArray
+15014,'%s' is not a JavaScript object
+15015,'%s' is not an enumerator object
+15016,'%s' is not a regular expression object
+15028,%s is not an Array or arguments object
+15031,%s is not an Array object
+15036,'%s' attribute on the property descriptor cannot be set to 'true' on this object
+15037,'%s' attribute on the property descriptor cannot be set to 'false' on this object
+15039,Redeclaration of const '%s'
+15041,Calling delete on '%s' is not allowed in strict mode
+15047,Unable to set property '%s' of undefined or null reference
+15048,Unable to get property '%s' of undefined or null reference
+15049,Unable to delete property '%s' of undefined or null reference
+15050,Unable to access property '%s': type 'VarDate' does not support user-defined properties
+15051,The value of the property '%s' is not a Function object
+15052,The value of the property '%s' is null or undefined, not a Function object
+15054,%s: 'this' is null or undefined
+15055,%s: 'this' is not an Object
+15056,%s: 'this' is not a Function object
+15057,%s: 'this' is not a String object
+15058,%s: 'this' is not a Boolean object
+15059,%s: 'this' is not a Date object
+15060,%s: 'this' is not a Number object
+15061,%s: 'this' is not a VBArray object
+15062,%s: 'this' is not a JavaScript object
+15063,%s: 'this' is not an Enumerator object
+15064,%s: 'this' is not a RegExp object
+15065,%s: invalid argument
+15066,%s: argument is not an Object
+15067,%s: argument is not a JavaScript object
+15068,%s: argument is not a Function object
+15069,%s: argument is not a VBArray object
+15070,%s: argument is null or undefined
+15071,%s: argument is not an Object and is not null
+15072,%s: argument does not have a valid 'length' property
+15073,%s: Array or arguments object expected
+15074,Invalid operand to '%s': Object expected
+15075,Invalid operand to '%s': Function expected
+15076,Invalid descriptor for property '%s'
+15077,Cannot define property '%s': object is not extensible
+15078,Cannot redefine non-configurable property '%s'
+15079,Cannot modify non-writable property '%s'
+15080,Cannot modify property '%s': 'length' is not writable
+15081,Cannot define property '%s'
+15088,Required argument %s in DataView method is not specified
+15090,DataView constructor argument %s is invalid
+15091,The function '%s' has an invalid signature and cannot be called
+15092,The property '%s' has an invalid signature and cannot be accessed
+15093,The runtimeclass %s that has Windows.Foundation.IPropertyValue as default interface is not supported as input parameter type
+15094,The object with interface Windows.Foundation.IPropertyValue that has runtimeclass name %s is not supported as out parameter
+15096,%s: 'this' is not an Inspectable Object
+15097,%s: could not convert argument to type 'char'
+15098,%s: could not convert argument to type 'GUID'
+15099,%s: could not convert return value to IInspectable
+15100,Could not convert object to struct: object missing expected property '%s'
+15101,Type '%s' not found
+15102,%s: function called with too few arguments
+15103,%s: type is not constructible
+15104,Could not convert value to PropertyValue: %s not supported by PropertyValue
+15105,Could not convert value to IInspectable: %s not supported by IInspectable
+15108,%s: The Inspectable object 'this' is released and cannot be accessed
+15110,'this' is not of expected type: %s
+15112,%s: an unexpected failure occurred while trying to obtain metadata information
+32812,The specified date is not available in the current locale's calendar
diff --git a/src/sentry/data/error-locale/hu-HU.txt b/src/sentry/data/error-locale/hu-HU.txt
new file mode 100644
index 00000000000000..553bb5e51b0027
--- /dev/null
+++ b/src/sentry/data/error-locale/hu-HU.txt
@@ -0,0 +1,289 @@
+5,Érvénytelen eljáráshívás vagy argumentum
+6,Túlcsordulás
+7,Kevés a memória.
+9,Az index kívül esik a tartományon.
+10,Ez a tömb rögzített vagy ideiglenesen zárolva van.
+11,Nullával való osztás
+13,Típuseltérés
+14,A karakterlánc túl hosszú.
+17,A megadott művelet nem hajtható végre.
+28,Nincs elég veremmemória.
+35,Nem definiált Sub vagy Function
+48,Hiba lépett fel a DLL betöltése közben.
+51,Belső hiba
+52,Helytelen fájlnév vagy -szám
+53,A fájl nem található.
+54,Helytelen fájlmód
+55,A fájl már meg van nyitva.
+57,Eszköz I/O-hibája
+58,A fájl már létezik.
+61,A lemez megtelt.
+62,A bevitel túllépte a fájl végét.
+67,Túl sok fájl
+68,Az eszköz nem áll készen.
+70,Engedély megtagadva
+71,A lemez nem áll készen.
+74,Az átnevezés nem vonatkozhat a meghajtó betűjelére.
+75,A fájlt vagy könyvtárat nem lehet elérni.
+76,Az elérési út nem található.
+91,Az objektumváltozó vagy a With blokk változója nincs beállítva.
+92,A For ciklus nincs inicializálva.
+94,A Null szabálytalan használata
+322,A szükséges ideiglenes fájl nem hozható létre.
+424,Az objektumot kötelező megadni.
+429,Az automatizálható szolgáltató nem tudja létrehozni az objektumot.
+430,Az osztály nem automatizálható.
+432,A fájlnév vagy objektumnév nem található az automatizálási művelet közben.
+438,Az objektum nem támogatja ezt a tulajdonságot vagy metódust.
+440,Automatizálási hiba
+445,Az objektum nem támogatja a műveletet.
+446,Az objektum nem támogatja az elnevezett argumentumokat.
+447,Az objektum nem támogatja az aktuális területi beállításokat.
+448,Az elnevezett argumentum nem található.
+449,Az argumentumot kötelező megadni.
+450,Nem megfelelő számú argumentum, vagy érvénytelen tulajdonság-hozzárendelés.
+451,Az objektum nem gyűjtemény.
+453,A megadott DLL nem található.
+458,A JavaScript nem támogatja a változó által használt automatizálási típust
+462,A távoli kiszolgáló számítógép nem létezik vagy nem érhető el.
+501,Nem rendelhető változóhoz.
+502,Az objektum nem használható biztonságosan szkriptben.
+503,Az objektum nem inicializálható biztonságosan.
+504,Az objektumot nem lehet biztonságosan létrehozni.
+507,Kivétel történt.
+1001,Kevés a memória.
+1002,Szintaktikai hiba
+1003,A várt elem ':'
+1004,A várt elem ';'
+1005,A várt elem '('
+1006,A várt elem ')'
+1007,A várt elem ']'
+1008,A várt elem '{'
+1009,A várt elem '}'
+1010,A várt elem azonosító
+1011,A várt elem '='
+1012,A várt elem '/'
+1013,Érvénytelen szám
+1014,Érvénytelen karakter
+1015,Lezáratlan karakterlánckonstans
+1016,Lezáratlan megjegyzés
+1018,Függvényen kívüli 'return' utasítás
+1019,A 'break' utasítás nem szerepelhet cikluson kívül.
+1020,A 'continue' utasítás nem szerepelhet cikluson kívül.
+1023,A várt elem hexadecimális szám
+1024,A várt elem 'while'
+1025,A címke újra lett definiálva.
+1026,A címke nem található.
+1027,A 'default' utasítás csak egyszer szerepelhet egy 'switch' blokkon belül.
+1028,A várt elem azonosító, karakterlánc vagy szám
+1029,A várt elem '@end'
+1030,A feltételes fordítás ki van kapcsolva.
+1031,A várt elem konstans
+1032,A várt elem '@'
+1033,A várt elem 'catch'
+1034,A várt elem 'var'
+1035,A "throw" utasítás után a forrás ugyanazon sorában kifejezésnek kell szerepelnie
+1037,A "with" utasítás nem engedélyezett szigorú üzemmódban
+1038,Az ismétlődő formális paraméternevek nem engedélyezettek szigorú üzemmódban
+1039,Az oktális numerikus konstansok és escape-karakterek nem engedélyezettek szigorú üzemmódban
+1041,Az eval használata érvénytelen szigorú üzemmódban
+1042,Az 'arguments' használata érvénytelen szigorú üzemmódban
+1045,A delete függvény nem hívható meg kifejezésben szigorú üzemmód esetén
+1046,Egy tulajdonság nem definiálható többször szigorú üzemmód esetén
+1047,Szigorú üzemmódban a függvénydeklarációk nem ágyazhatók be egy utasításba vagy blokkba. Csak a legfelső szinten vagy közvetlenül a függvénytörzsben jelenhetnek meg.
+1048,Kulcsszó használata azonosítóként érvénytelen
+1049,Leendő foglalt kulcsszó használata azonosítóként érvénytelen
+1050,Leendő foglalt kulcsszó használata azonosítóként érvénytelen. Az azonosító neve szigorú üzemmódban foglalt.
+1051,A beállító függvényeknek egy argumentumuk lehet
+4096,JavaScript - fordítási hiba
+4097,JavaScript - futásidejű hiba
+4098,Ismeretlen futásidejű hiba
+5000,Nem rendelhető a 'this' objektumhoz.
+5001,A várt elem szám
+5002,A várt elem függvény
+5003,Nem rendelhető függvényeredményhez.
+5004,Az objektum nem indexelhető.
+5005,A várt elem karakterlánc
+5006,A várt elem dátumobjektum
+5007,A várt elem objektum
+5008,Érvénytelen a hozzárendelés bal oldala
+5009,Nem definiált azonosító
+5010,A várt elem logikai változó
+5011,Felszabadított szkriptben nem hajtható végre kód.
+5012,A várt elem objektumtag
+5013,A várt elem VBArray
+5014,A várt elem JavaScript-objektum
+5015,A várt elem enumerátorobjektum
+5016,A várt elem reguláris kifejezés
+5017,Szintaktikai hiba a reguláris kifejezésben
+5018,Váratlan ismétlődésleíró
+5019,Hiányzó ']' a reguláris kifejezésben
+5020,Hiányzó ')' a reguláris kifejezésben
+5021,Érvénytelen tartomány a karakterkészletben
+5022,Kezeletlen kivétel.
+5023,A függvénynek nincs érvényes prototípusa.
+5024,A kódolandó URI érvénytelen karaktert tartalmaz
+5025,A dekódolandó URI kódolása érvénytelen.
+5026,A tizedesjegyek száma nincs a megengedett tartományban
+5027,A tizedespontosság nincs a megengedett tartományban
+5028,A várt elem tömb vagy argumentumobjektum
+5029,A tömb hossza csak véges pozitív egész szám lehet.
+5030,A tömb hossza csak véges pozitív számként adható meg.
+5031,A várt elem tömbobjektum
+5034,A körkörös hivatkozás érték argumentumnál nem támogatott
+5035,Érvénytelen lecserélő argumentum
+5038,Az argumentumlista túl hosszú, ezért nem alkalmazható
+5039,Újra meg lett határozva a const tulajdonság
+5041,Az objektumtag nem konfigurálható
+5042,A változó nincs meghatározva szigorú üzemmódban
+5043,A függvények vagy argumentumobjektumok caller tulajdonságának lekérdezése nem engedélyezett szigorú üzemmódban
+5044,Az argumentumobjektumok callee tulajdonságának lekérdezése nem engedélyezett szigorú üzemmódban
+5045,A csak olvasható tulajdonságok értékadása nem engedélyezett szigorú üzemmódban
+5046,Nem hozható létre tulajdonság egy nem kiterjeszthető objektum esetén
+5047,A várt elem objektum
+5048,A várt elem objektum
+5049,A várt elem objektum
+5050,A várt elem objektum
+5051,A várt elem függvény
+5052,A várt elem függvény
+5053,A tulajdonságnak nem lehet egyszerre értéke és elérője is
+5054,'this' értéke null vagy nincs meghatározva
+5055,A várt elem objektum
+5056,A várt elem függvény
+5057,A várt elem karakterlánc
+5058,A várt elem logikai változó
+5059,A várt elem dátum
+5060,A várt elem szám
+5061,A várt elem VBArray
+5062,A várt elem JavaScript-objektum
+5063,A várt elem enumerátorobjektum
+5064,A várt elem reguláris kifejezés objektum
+5065,Érvénytelen függvényargumentum
+5066,A várt elem objektum
+5067,A várt elem JavaScript-objektum
+5068,A várt elem függvény
+5069,A várt elem VBArray
+5070,A várt elem objektum
+5071,A várt elem objektum
+5072,Érvénytelen length tulajdonság
+5073,A várt elem tömb vagy argumentumobjektum
+5074,Érvénytelen operandus
+5075,Érvénytelen operandus
+5076,Érvénytelen a tulajdonság leírója
+5077,Nem lehet meghatározni a tulajdonságot: az objektum nem kiterjeszthető
+5078,Nem lehet újra meghatározni a nem konfigurálható tulajdonságot
+5079,Nem lehet módosítani a nem írható tulajdonságot
+5080,Nem lehet módosítani a tulajdonságot: a "length" nem írható
+5081,Nem lehet definiálni a tulajdonságot
+5082,A típusos tömb konstruktorargumentuma érvénytelen
+5083,A 'this' nem típusos tömb objektum
+5084,A program a típusos tömb létrehozásakor érvénytelen eltolást vagy hosszt talált
+5085,Egy típusos tömb tömbrészképző metódusa érvénytelen kezdő vagy befejező értéket kapott
+5086,Érvénytelen a forrás a típusostömb-készletben
+5087,A 'this' nem DataView objektum
+5088,Érvénytelen argumentumok a DataView objektumban
+5089,A DataView műveletei túlnyúlnak a megadott pufferhosszúságon
+5090,Érvénytelen argumentumok a DataView objektumban
+5091,érvénytelen függvényaláírás
+5092,érvénytelen tulajdonság-aláírás
+5093,érvénytelen bemenetiparaméter-típus
+5094,érvénytelen kimenetiparaméter-típus
+5095,A függvények arguments tulajdonságának lekérdezése nem engedélyezett szigorú üzemmódban
+5096,A program vizsgálható objektumot várt
+5097,Nem sikerült az argumentum 'char' típusúvá konvertálása
+5098,Nem sikerült az argumentum 'GUID' típusúvá konvertálása
+5099,A program IInspectable elemet várt
+5100,Nem sikerült az objektum struktúrává konvertálása: az objektumból hiányzik egy várt tulajdonság
+5101,Ismeretlen típus
+5102,A függvény túl kevés argumentummal lett meghívva
+5103,Ez nem példányosítható típus
+5104,Nem sikerült az érték PropertyValue típusúvá konvertálása: a típust a PropertyValue osztály nem támogatja
+5105,Nem sikerült az érték IInspectable típusúvá konvertálása: a típust az IInspectable osztály nem támogatja
+5106,Nem sikerült a dátum Windows.Foundation.DateTime típusúvá konvertálása: az érték kívül esik az érvényes tartományon
+5107,Nem sikerült az érték Windows.Foundation.TimeSpan típusúvá konvertálása: az érték kívül esik az érvényes tartományon
+5108,Érvénytelen hozzáférés a már felszabadított vizsgálható objektumhoz
+5109,Nem szabadítható fel a már felszabadított vizsgálható objektum
+5110,A 'this' nem várt típus
+5111,Szabálytalan a tömb megadott hossza és mérete
+5112,Váratlan hiba történt a metaadat-információ beszerzése során
+5200,Az állapot "error", de a getResults metódus nem adott vissza hibát
+5201,A program hiányzó vagy érvénytelen állapotparamétert adott át a befejezést kezelő leírónak
+5202,A program hiányzó vagy érvénytelen, küldőre vonatkozó paramétert adott át a befejezést kezelő leírónak
+6000,Végtelen
+6001,-Végtelen
+10438,Az objektum nem támogatja a következő tulajdonságot vagy metódust: '%s'
+10449,A(z) '%s' függvénynek kötelező argumentumot adni
+15001,'%s' nem szám
+15002,'%s' nem függvény
+15004,'%s' nem indexelhető objektum
+15005,'%s' nem karakterlánc
+15006,'%s' nem dátumobjektum
+15007,'%s' értéke NULL, vagy nem objektum
+15008,Nem lehet hozzárendelni a következőhöz: '%s'
+15009,'%s' nincs definiálva
+15010,'%s' nem logikai változó
+15012,'%s' nem törölhető
+15013,'%s' nem VBArray
+15014,A(z) '%s' nem JavaScript-objektum
+15015,'%s' nem enumerátorobjektum
+15016,'%s' nem reguláris kifejezés
+15028,%s nem tömb vagy argumentumobjektum
+15031,%s nem tömbobjektum
+15036,A tulajdonságleíró '%s' attribútuma nem állítható be "True" értékre ehhez az objektumhoz
+15037,A tulajdonságleíró '%s' attribútuma nem állítható be "False" értékre ehhez az objektumhoz
+15039,Újra meg lett határozva a(z) '%s' konstans
+15041,%s esetében nem hívható delete függvény szigorú üzemmódban
+15047,Nem lehet beállítani a(z) %s tulajdonságot, amelyhez nem definiált vagy null értékű hivatkozás tartozik
+15048,Nem lehet lekérni a(z) %s tulajdonságot, amelyhez nem definiált vagy null értékű hivatkozás tartozik
+15049,Nem lehet törölni a(z) %s tulajdonságot, amelyhez nem definiált vagy null értékű hivatkozás tartozik
+15050,Nem érhető el a(z) '%s' tulajdonság: VarDate típus esetén nem használható felhasználó által meghatározott tulajdonság
+15051,A(z) '%s' tulajdonság értéke nem függvényobjektum
+15052,A(z) '%s' tulajdonság értéke null vagy nincs meghatározva, nem pedig függvényobjektum
+15054,%s: 'this' null értékű vagy nincs meghatározva
+15055,%s: 'this' nem objektum
+15056,%s: 'this' nem függvényobjektum
+15057,%s: 'this' nem karakterlánc-objektum
+15058,%s: 'this' nem logikai objektum
+15059,%s: 'this' nem dátumobjektum
+15060,%s: 'this' nem numerikus objektum
+15061,%s: 'this' nem VBArray-objektum
+15062,%s: 'this' nem JavaScript-objektum
+15063,%s: 'this' nem enumerátorobjektum
+15064,%s: 'this' nem reguláris kifejezés objektum
+15065,%s: érvénytelen argumentum
+15066,%s: az argumentum nem objektum
+15067,%s: az argumentum nem JavaScript-objektum
+15068,%s: az argumentum nem függvényobjektum
+15069,%s: az argumentum nem VBArray-objektum
+15070,%s: az argumentum null értékű vagy nincs meghatározva
+15071,%s: az argumentum nem objektum és nem null értékű
+15072,%s: az argumentumnak nincs érvényes "length" tulajdonsága
+15073,%s: a várt elem tömb vagy argumentumobjektum
+15074,Érvénytelen a(z) '%s' operandusa: a várt elem objektum
+15075,Érvénytelen a(z) '%s' operandusa: a várt elem függvény
+15076,Érvénytelen a(z) '%s' tulajdonság leírója
+15077,Nem lehet meghatározni a(z) '%s' tulajdonságot: az objektum nem kiterjeszthető
+15078,Nem lehet újra meghatározni a(z) '%s' nem konfigurálható tulajdonságot
+15079,Nem lehet módosítani a(z) '%s' nem írható tulajdonságot
+15080,Nem lehet módosítani a(z) '%s' tulajdonságot: a "length" nem írható
+15081,Nem definiálható a(z) '%s' tulajdonság
+15088,Nincs megadva a DataView metódus kötelező %s argumentuma
+15090,Érvénytelen a DataView %s konstruktorargumentuma
+15091,A(z) %s függvény aláírása érvénytelen, és a függvény nem hívható
+15092,A(z) %s tulajdonság aláírása érvénytelen, és a tulajdonság nem kérdezhető le
+15093,A Windows.Foundation.IPropertyValue alapértelmezett felületű %s futásidejű osztályt a program nem támogatja bemenetiparaméter-típusként
+15094,A(z) %s futásidejűosztály-névvel rendelkező Windows.Foundation.IPropertyValue felületű objektumot a program nem támogatja kimeneti paraméterként
+15096,%s: a 'this' nem vizsgálható objektum
+15097,%s: nem sikerült az argumentum 'char' típusúvá konvertálása
+15098,%s: nem sikerült az argumentum 'GUID' típusúvá konvertálása
+15099,%s: nem sikerült a visszatérési érték IInspectable típusúvá konvertálása
+15100,Nem sikerült az objektum struktúrává konvertálása: az objektumból hiányzik a következő várt tulajdonság: '%s'
+15101,Nem található a(z) '%s' típus
+15102,%s: a függvény túl kevés argumentummal lett meghívva
+15103,%s: ez nem példányosítható típus
+15104,Nem sikerült az érték PropertyValue típusúvá konvertálása: a(z) %s típust a PropertyValue osztály nem támogatja
+15105,Nem sikerült az érték IInspectable típusúvá konvertálása: a(z) %s típust az IInspectable osztály nem támogatja
+15108,%s: a 'this' vizsgálható objektum fel van szabadítva és nem érhető el
+15110,A 'this' nem várt típus: %s
+15112,%s: váratlan hiba történt a metaadat-információ beszerzése során
+32812,A megadott dátum nem található az aktuális nyelv naptárában
diff --git a/src/sentry/data/error-locale/it-IT.txt b/src/sentry/data/error-locale/it-IT.txt
new file mode 100644
index 00000000000000..c4af8fc21f51a3
--- /dev/null
+++ b/src/sentry/data/error-locale/it-IT.txt
@@ -0,0 +1,289 @@
+5,Chiamata di routine o argomento non validi
+6,Overflow
+7,Memoria esaurita
+9,Indice non incluso nell'intervallo
+10,Questa matrice è fissa o temporaneamente bloccata
+11,Divisione per zero
+13,Tipo non corrispondente
+14,Spazio stringa esaurito
+17,Impossibile eseguire l'operazione richiesta
+28,Spazio dello stack esaurito
+35,Sub o Function non definita
+48,Errore di caricamento DLL
+51,Errore interno
+52,Nome o numero di file non valido
+53,Impossibile trovare il file
+54,Modalità file non valida
+55,File già aperto
+57,Errore di I/O periferica
+58,File già esistente
+61,Disco pieno
+62,Input oltre la fine del file
+67,Troppi file
+68,Periferica non disponibile
+70,Autorizzazione negata
+71,Disco non pronto
+74,Impossibile rinominare con unità diversa
+75,Errore di accesso al percorso/file
+76,Impossibile trovare il percorso
+91,Variabile oggetto o variabile del blocco With non impostata
+92,Ciclo For non inizializzato
+94,Utilizzo non valido di Null
+322,Impossibile creare il file temporaneo necessario
+424,Necessario oggetto
+429,Il server di automazione non può creare l'oggetto
+430,La classe non supporta l'automazione
+432,Impossibile trovare nome di file o nome di classe durante un'operazione di automazione
+438,Proprietà o metodo non supportati dall'oggetto
+440,Errore di automazione
+445,Azione non valida per l'oggetto
+446,L'oggetto non supporta argomenti predefiniti
+447,L'oggetto non supporta le impostazioni internazionali correnti
+448,Impossibile trovare l'argomento predefinito
+449,Argomento non facoltativo
+450,Numero errato di argomenti o assegnazione di proprietà non valida
+451,L'oggetto non è un insieme
+453,Impossibile trovare nella DLL la funzione specificata
+458,La variabile utilizza un tipo di automazione non supportato in JavaScript
+462,Il computer server remoto non esiste o non è disponibile
+501,Impossibile assegnare alla variabile
+502,Oggetto non sicuro per la creazione di script
+503,Oggetto non sicuro per l'inizializzazione
+504,Oggetto non sicuro per la creazione
+507,Si è verificata un'eccezione
+1001,Memoria esaurita
+1002,Errore di sintassi
+1003,Previsto ':'
+1004,Previsto ';'
+1005,Previsto '('
+1006,Previsto ')'
+1007,Previsto ']'
+1008,Previsto '{'
+1009,Previsto '}'
+1010,Previsto identificatore
+1011,Previsto '='
+1012,Previsto '/'
+1013,Numero non valido
+1014,Carattere non valido
+1015,Costante String senza terminazione
+1016,Commento senza terminazione
+1018,Istruzione 'return' esterna alla funzione
+1019,Impossibile utilizzare 'break' all'esterno di un ciclo
+1020,Impossibile utilizzare 'continue' all'esterno di un ciclo
+1023,Prevista cifra esadecimale
+1024,Previsto 'while'
+1025,Etichetta ridefinita
+1026,Impossibile trovare l'etichetta
+1027,'default' può apparire una volta sola in una istruzione 'switch'
+1028,Previsto identificatore, stringa o numero
+1029,Previsto '@end'
+1030,Compilazione condizionale disattivata
+1031,Prevista costante
+1032,Previsto '@'
+1033,Previsto 'catch'
+1034,Previsto 'var'
+1035,la parola chiave 'throw' deve essere seguita da un'espressione nella stessa riga di codice sorgente
+1037,le istruzioni 'with' non sono consentite in modalità strict
+1038,Nomi parametri formali duplicati non consentiti in modalità strict
+1039,Letterali numerici ottali e caratteri escape non consentiti in modalità strict
+1041,Uso non valido di 'eval' in modalità strict
+1042,Uso non valido di 'arguments' in modalità strict
+1045,Uso funzionalità di eliminazione nell'espressione non consentito in modalità strict
+1046,Più definizioni di una proprietà non consentite in modalità strict
+1047,Nella modalità strict le dichiarazioni di funzioni non possono essere nidificate in un'istruzione o un blocco. Possono essere presenti solo nel livello superiore o direttamente nel corpo di una funzione.
+1048,Una parola chiave non può essere utilizzata come identificativo
+1049,Una parola riservata per il futuro non può essere utilizzata come identificativo
+1050,Una parola riservata per il futuro non può essere utilizzata come identificativo. Il nome dell'identificativo è riservato in modalità strict.
+1051,Le funzioni setter devono avere un solo argomento
+4096,Errore di compilazione di JavaScript
+4097,Errore di run-time di JavaScript
+4098,Errore di run-time sconosciuto
+5000,Impossibile assegnare a 'this'
+5001,Previsto numero
+5002,Prevista funzione
+5003,Impossibile assegnare al risultato di una funzione
+5004,Impossibile indicizzare l'oggetto
+5005,Prevista stringa
+5006,Previsto oggetto data
+5007,Previsto oggetto
+5008,Lato sinistro non valido nell'assegnazione
+5009,Identificatore non definito
+5010,Previsto valore Boolean
+5011,Impossibile eseguire il codice da uno script liberato
+5012,Previsto membro oggetto
+5013,Prevista VBArray
+5014,Previsto oggetto JavaScript
+5015,Previsto oggetto enumeratore
+5016,Previsto un oggetto espressione regolare
+5017,Errore di sintassi nell'espressione regolare
+5018,Quantificatore imprevisto
+5019,Prevista ']' nell'espressione regolare
+5020,Prevista ')' nell'espressione regolare
+5021,Intervallo non valido nel set di caratteri
+5022,Eccezione formulata con throw ma senza catch
+5023,Alla funzione non è associato nessun oggetto prototipo valido
+5024,L'URI da codificare contiene un carattere non valido
+5025,La codifica utilizzata per l'URI da decodificare non è corretta
+5026,Numero di cifre frazionarie non incluso nell'intervallo
+5027,Approssimazione non inclusa nell'intervallo
+5028,Previsto oggetto Array o arguments
+5029,La lunghezza della matrice deve essere un intero positivo finito
+5030,Alla lunghezza della matrice deve essere assegnato un numero positivo finito
+5031,Previsto oggetto Array
+5034,Riferimento circolare nell'argomento value non supportato
+5035,Argomento replacer non valido
+5038,Elenco argomenti troppo esteso per essere applicato
+5039,Ridichiarazione proprietà const
+5041,Membro oggetto non configurabile
+5042,Variabile non definita in modalità strict
+5043,Accesso proprietà 'caller' non consentito in modalità strict
+5044,Accesso proprietà 'callee' non consentito in modalità strict
+5045,Assegnazione a proprietà di sola lettura non consentita in modalità strict
+5046,Impossibile creare una proprietà per un oggetto non estensibile
+5047,Previsto oggetto
+5048,Previsto oggetto
+5049,Previsto oggetto
+5050,Previsto oggetto
+5051,Prevista funzione
+5052,Prevista funzione
+5053,La proprietà non può avere sia funzioni di accesso sia un valore
+5054,'this' è nullo o non definito
+5055,Previsto oggetto
+5056,Prevista funzione
+5057,Prevista stringa
+5058,Previsto valore booleano
+5059,Data prevista
+5060,Previsto numero
+5061,Previsto VBArray
+5062,Previsto oggetto JavaScript
+5063,Previsto oggetto enumeratore
+5064,Previsto oggetto RegExp
+5065,Argomento funzione non valido
+5066,Previsto oggetto
+5067,Previsto oggetto JavaScript
+5068,Prevista funzione
+5069,Previsto VBArray
+5070,Previsto oggetto
+5071,Previsto oggetto
+5072,Proprietà 'length' non valida
+5073,Previsto oggetto array o argomenti
+5074,Operando non valido
+5075,Operando non valido
+5076,Descrittore proprietà non valido
+5077,Impossibile definire la proprietà: l'oggetto non è estendibile
+5078,Impossibile ridefinire una proprietà non configurabile
+5079,Impossibile modificare una proprietà a cui non si può accedere in scrittura
+5080,Impossibile modificare la proprietà: 'length' non è accessibile in scrittura
+5081,Impossibile definire la proprietà
+5082,L'argomento del costruttore della matrice tipizzata non è valido
+5083,'this' non è un oggetto di matrice tipizzata
+5084,Offset/lunghezza non valida per la creazione di una matrice tipizzata
+5085,Valore inizio/fine non valido nel metodo della matrice secondaria della matrice tipizzata
+5086,Tipo di origine non valido nel set di matrici tipizzate
+5087,'this' non è un oggetto DataView
+5088,Argomenti non validi in DataView
+5089,L'accesso all'operazione DataView supera la lunghezza del buffer specificato
+5090,Argomenti non validi in DataView
+5091,firma di funzione non valida
+5092,firma di proprietà non valida
+5093,tipo di parametro di input non valido
+5094,parametro di input non valido
+5095,In modalità strict non è consentito accedere alla proprietà 'arguments' di una funzione
+5096,Previsto oggetto Inspectable
+5097,Impossibile convertire l'argomento nel tipo 'char'
+5098,Impossibile convertire l'argomento nel tipo 'GUID'
+5099,Previsto IInspectable
+5100,Impossibile convertire l'oggetto in struct: proprietà prevista dell'oggetto mancante
+5101,Tipo sconosciuto
+5102,Funzione chiamata con troppo pochi argomenti
+5103,Tipo non costruibile
+5104,Impossibile convertire il valore in PropertyValue. Tipo non supportato da PropertyValue
+5105,Impossibile convertire il valore in IInspectable. Tipo non supportato da IInspectable
+5106,Impossibile convertire la data in Windows.Foundation.DateTime. Valore non compreso nell'intervallo valido
+5107,Impossibile convertire il valore in Windows.Foundation.TimeSpan. Valore non compreso nell'intervallo valido
+5108,Accesso non valido all'oggetto Inspectable già rilasciato
+5109,Impossibile rilasciare l'oggetto Inspectable già rilasciato
+5110,'this' non è del tipo previsto
+5111,Specificate lunghezza e dimensioni non valide per l'array
+5112,Errore imprevisto durante il recupero di informazioni sui metadati
+5200,Lo stato è 'error', ma getResults non ha restituito alcun errore
+5201,Passato parametro status non valido o mancante al gestore completato
+5202,Passato parametro sender non valido o mancante al gestore completato
+6000,Infinity
+6001,-Infinity
+10438,L'oggetto non supporta la proprietà o il metodo '%s'
+10449,L'argomento per la funzione '%s' non è facoltativo
+15001,'%s' non è un numero
+15002,'%s' non è una funzione
+15004,'%s' non è un oggetto indicizzabile
+15005,'%s' non è una stringa
+15006,'%s' non è un oggetto data
+15007,'%s' è nullo o non è un oggetto
+15008,Impossibile assegnare a '%s'
+15009,'%s' non è definito
+15010,'%s' non è un valore booleano
+15012,Impossibile eliminare '%s'
+15013,'%s' non è una VBArray
+15014,'%s' non è un oggetto JavaScript
+15015,'%s' non è un oggetto enumeratore
+15016,'%s' non è un oggetto espressione regolare
+15028,%s non è un oggetto Array o arguments
+15031,%s non è un oggetto Array
+15036,Impossibile impostare l'attributo '%s' del descrittore di proprietà su 'true' per questo oggetto
+15037,Impossibile impostare l'attributo '%s' del descrittore di proprietà su 'false' per questo oggetto
+15039,Ridichiarazione costante '%s'
+15041,Uso funzionalità di eliminazione in '%s' non è consentito in modalità strict
+15047,Impossibile impostare la proprietà '%s' di un riferimento nullo o non definito
+15048,Impossibile recuperare la proprietà '%s' di un riferimento nullo o non definito
+15049,Impossibile eliminare la proprietà '%s' di un riferimento nullo o non definito
+15050,Impossibile accedere alla proprietà '%s': 'VarDate' non supporta le proprietà definite dall'utente
+15051,Il valore della proprietà '%s' non è un oggetto funzione
+15052,Il valore della proprietà '%s' è nullo o non definito, non è un oggetto funzione
+15054,%s: 'this' è nullo o non definito
+15055,%s: 'this' non è un oggetto
+15056,%s: 'this' non è un oggetto funzione
+15057,%s: 'this' non è un oggetto stringa
+15058,%s: 'this' non è un oggetto booleano
+15059,%s: 'this' non è un oggetto data
+15060,%s: 'this' non è un oggetto numero
+15061,%s: 'this' non è un oggetto VBArray
+15062,%s: 'this' non è un oggetto JavaScript
+15063,%s: 'this' non è un oggetto enumeratore
+15064,%s: 'this' non è un oggetto RegExp
+15065,%s: argomento non valido
+15066,%s: l'argomento non è un oggetto
+15067,%s: l'argomento non è un oggetto JavaScript
+15068,%s: l'argomento non è un oggetto funzione
+15069,%s: l'argomento non è un oggetto VBArray
+15070,%s: l'argomento è nullo o non definito
+15071,%s: l'argomento non è un oggetto e non è nullo
+15072,%s: l'argomento non ha una proprietà 'length' valida
+15073,%s: previsto oggetto Array o arguments
+15074,Operando non valido per '%s': previsto oggetto
+15075,Operando non valido per '%s': prevista funzione
+15076,Descrittore non valido per la proprietà '%s'
+15077,Impossibile definire la proprietà '%s': l'oggetto non è estendibile
+15078,Impossibile ridefinire la proprietà '%s' non configurabile
+15079,Impossibile modificare la proprietà '%s' a cui non si può accedere in scrittura
+15080,Impossibile modificare la proprietà '%s': 'length' non è accessibile in scrittura
+15081,Impossibile definire la proprietà '%s'
+15088,L'argomento obbligatorio %s nel metodo DataView non è specificato
+15090,L'argomento %s del costruttore di DataView non è valido
+15091,La funzione '%s' ha una firma non valida e non può essere chiamata
+15092,La proprietà '%s' ha una firma non valida ed è impossibile accedervi
+15093,Runtimeclass %s con l'interfaccia predefinita Windows.Foundation.IPropertyValue non è supportato come tipo di parametro di input
+15094,L'oggetto con interfaccia Windows.Foundation.IPropertyValue con nome runtimeclass %s non è supportato come parametro di output
+15096,%s: 'this' non è un oggetto Inspectable
+15097,%s: impossibile convertire l'argomento nel tipo 'char'
+15098,%s: impossibile convertire l'argomento nel tipo 'GUID'
+15099,%s: impossibile convertire il valore restituito in IInspectable
+15100,Impossibile convertire l'oggetto in struct: proprietà prevista dell'oggetto '%s' mancante
+15101,Tipo '%s' non trovato
+15102,%s: funzione chiamata con troppo pochi argomenti
+15103,%s: tipo non costruibile
+15104,Impossibile convertire il valore in PropertyValue. %s non supportato da PropertyValue
+15105,Impossibile convertire il valore in IInspectable. %s non supportato da IInspectable
+15108,%s: l'oggetto Inspectable 'this' è rilasciato e non è possibile accedervi
+15110,'this' non è del tipo previsto: %s
+15112,%s: errore imprevisto durante il recupero di informazioni sui metadati
+32812,La data specificata non è disponibile nel calendario delle impostazioni internazionali correnti
diff --git a/src/sentry/data/error-locale/ja-JP.txt b/src/sentry/data/error-locale/ja-JP.txt
new file mode 100644
index 00000000000000..12a17801abadc7
--- /dev/null
+++ b/src/sentry/data/error-locale/ja-JP.txt
@@ -0,0 +1,289 @@
+5,プロシージャの呼び出し、または引数が不正です。
+6,オーバーフローしました。
+7,メモリが不足しています。
+9,インデックスが有効範囲にありません。
+10,固定サイズの配列、または現在、この配列はサイズを変更できない状態にあります。
+11,0 で除算しました。
+13,型が一致しません。
+14,文字列領域が不足しています。
+17,要求された操作を実行できません。
+28,スタック領域が不足しています。
+35,Sub または Function が定義されていません。
+48,DLL 読み込み時のエラーです。
+51,内部エラーです。
+52,ファイルの名前または数が不正です。
+53,ファイルが見つかりません。
+54,ファイルのモードが不正です。
+55,ファイルは既に開かれています。
+57,デバイス I/O (入出力) エラーです。
+58,既に同名のファイルが存在しています。
+61,ディスクの空き容量が不足しています。
+62,ファイルの最後を超えた入力を行おうとしました。
+67,ファイル数が多すぎます。
+68,このデバイスは使用できません。
+70,書き込みできません。
+71,ディスクは準備されていません。
+74,異なったドライブでも名前の変更はできません。
+75,パスまたはファイル名が無効です。
+76,パスが見つかりません。
+91,オブジェクト型の変数または With ブロックの変数は設定されていません。
+92,For ループは初期化されていません。
+94,Null 値の使い方が不正です。
+322,必要なテンポラリ ファイルが作成できません。
+424,オブジェクトがありません。
+429,オートメーション サーバーはオブジェクトを作成できません。
+430,クラスはオートメーションをサポートしていません。
+432,オートメーションの操作中にファイル名またはクラス名を見つけられませんでした。
+438,オブジェクトでサポートされていないプロパティまたはメソッドです。
+440,オートメーション エラーです。
+445,このオブジェクトではサポートされていない操作です。
+446,このオブジェクトは、名前付き引数をサポートしていません。
+447,現在の国別情報の設定は、このオブジェクトではサポートされていません。
+448,名前付き引数が不正です。
+449,引数は省略できません。
+450,引数の数が一致していません。または不正なプロパティを指定しています。
+451,オブジェクトがコレクションではありません。
+453,指定された DLL に関数が定義されていません。
+458,この変数には JavaScript でサポートされていない種類のオートメーションが使用されています。
+462,リモート サーバー マシンが存在しないか、利用できません。
+501,この変数に割り当てられません。
+502,このオブジェクトは safe モードでは作成できません。
+503,このオブジェクトは safe モードでは初期化できません。
+504,このオブジェクトは safe モードでは作成できません。
+507,例外が発生しました。
+1001,メモリが不足しています。
+1002,構文エラーです。
+1003,':' がありません。
+1004,';' がありません。
+1005,'(' がありません。
+1006,')' がありません。
+1007,']' がありません。
+1008,'{' がありません。
+1009,'}' がありません。
+1010,識別子がありません。
+1011,'=' がありません。
+1012,'/' がありません。
+1013,不正な数字です。
+1014,文字が正しくありません。
+1015,終了していない文字列型の定数です。
+1016,終了していないコメントです。
+1018,'return' ステートメントが関数の外側にあります。
+1019,'break' はループの外側には指定できません。
+1020,'continue' はループの外側には指定できません。
+1023,16 進数を指定してください。
+1024,'while' を指定してください。
+1025,この Label は既に定義されています。
+1026,この Label が定義されていません。
+1027,'default' は 'switch' ステートメントのなかでは、一度のみ表示されます。
+1028,識別子、文字列または数がありません。
+1029,'@end' がありません。
+1030,条件コンパイルは中止されます。
+1031,定数がありません。
+1032,'@' がありません。
+1033,'catch' ステートメントがありません。
+1034,'var' ステートメントがありません。
+1035,'throw' ステートメントに指定する式は、ソース コードの同一行に記述してください。
+1037,strict モードでは、'with' ステートメントは使用できません。
+1038,strict モードでは、重複する仮引数名は使用できません。
+1039,strict モードでは、8 進数リテラルとエスケープ文字は使用できません。
+1041,strict モードでの 'eval' の使用方法が無効です。
+1042,strict モードでの 'arguments' の使用方法が無効です。
+1045,strict モードでは、式で delete を呼び出すことはできません。
+1046,strict モードでは、プロパティの複数定義は許可されません。
+1047,strict モードでは、関数の宣言をステートメントまたはブロック内で入れ子にできません。関数の宣言を配置できるのは、トップ レベル、または直接関数の本体内のみです。
+1048,識別子にキーワードを使用することは無効です
+1049,識別子に予約語を使用することは無効です。
+1050,識別子に予約語を使用することは無効です。strict モードでは、識別子名が予約されています。
+1051,setter 関数には 1 つの引数が必要です
+4096,JavaScript コンパイル エラー
+4097,JavaScript 実行時エラー
+4098,未知の実行時エラーです。
+5000,'this' に割り当てることはできません。
+5001,数字を指定してください。
+5002,関数を指定してください。
+5003,関数の戻り値に割り当てることはできません。
+5004,オブジェクトにインデックスを付けられません。
+5005,文字列を指定してください。
+5006,Date オブジェクトを指定してください。
+5007,オブジェクトを指定してください。
+5008,左辺の代入が無効です。
+5009,定義されていない識別子です。
+5010,ブール型 (Boolean)を指定してください。
+5011,解放されたスクリプトからコードを実行できません。
+5012,オブジェクトのメンバーを指定してください。
+5013,VBArray を指定してください。
+5014,JavaScript オブジェクトを指定してください。
+5015,Enumerator オブジェクトを指定してください。
+5016,Regular Expression オブジェクトがありません。
+5017,正規表現で構文エラーが発生しました。
+5018,文字の繰り返しを表す正規表現演算子が不正です。
+5019,正規表現の中に ']' を指定してください。
+5020,正規表現の中に ')' を指定してください。
+5021,文字セットの範囲が不正です。
+5022,catch ステートメントでは適用されますが、throw ステートメントでは適用されません。
+5023,関数には、有効なプロトタイプ オブジェクトが存在しません。
+5024,エンコードする URI は無効な文字を含んでいます。
+5025,デコードする URI は有効なエンコーダーではありません。
+5026,小数の桁数が有効範囲を超えています。
+5027,有効桁数の範囲を超えています。
+5028,Array または arguments オブジェクトがありません。
+5029,配列の長さは、正の有限整数でなければなりません。
+5030,配列の長さには、正の有限数が割り当てられなければなりません。
+5031,Array オブジェクトがありません。
+5034,値引数の中の循環参照はサポートされていません
+5035,置換引数が無効です
+5038,引数リストが大きすぎて適用できません
+5039,const プロパティが再定義されています。
+5041,オブジェクトのメンバーが構成できません。
+5042,strict モードで、変数が未定義のままです。
+5043,strict モードでは、関数または arguments オブジェクトの 'caller' プロパティを使用できません
+5044,strict モードでは、arguments オブジェクトの 'callee' プロパティを使用できません
+5045,strict モードでは、読み取り専用プロパティに代入できません
+5046,非拡張可能オブジェクトのプロパティは作成できません。
+5047,オブジェクトを指定してください。
+5048,オブジェクトを指定してください。
+5049,オブジェクトを指定してください。
+5050,オブジェクトを指定してください。
+5051,関数を指定してください。
+5052,関数を指定してください。
+5053,プロパティは、アクセサーと値の両方を持つことはできません。
+5054,'this' が Null であるか未定義です。
+5055,オブジェクトを指定してください。
+5056,関数を指定してください。
+5057,文字列を指定してください。
+5058,ブール型 (Boolean)を指定してください。
+5059,Date を指定してください。
+5060,数字を指定してください。
+5061,VBArray を指定してください。
+5062,JavaScript オブジェクトを指定してください。
+5063,Enumerator オブジェクトを指定してください。
+5064,RegExp オブジェクトを指定してください。
+5065,関数の引数が無効です。
+5066,オブジェクトを指定してください。
+5067,JavaScript オブジェクトを指定してください。
+5068,関数を指定してください。
+5069,VBArray を指定してください。
+5070,オブジェクトを指定してください。
+5071,オブジェクトを指定してください。
+5072,'length' プロパティが無効です。
+5073,Array または arguments オブジェクトがありません。
+5074,オペランドが無効です。
+5075,オペランドが無効です。
+5076,プロパティ記述子が無効です。
+5077,プロパティを定義できません: オブジェクトは拡張可能ではありません
+5078,構成可能ではないプロパティは再定義できません
+5079,書き込み可能ではないプロパティは変更できません
+5080,プロパティを変更できません: 'length' は書き込み可能ではありません
+5081,プロパティを定義できません
+5082,型付き配列のコンストラクター引数が無効です
+5083,'this' は型付き配列オブジェクトではありません
+5084,型付き配列作成時のオフセット/長さが無効です
+5085,型付き配列の subarray メソッドの開始/終了値が無効です
+5086,型付き配列セットのソースが無効です
+5087,'this' は DataView オブジェクトではありません
+5088,DataView の引数が無効です
+5089,DataView 操作によるアクセスが指定したバッファー長を超えています
+5090,DataView の引数が無効です
+5091,関数のシグネチャが無効です
+5092,プロパティのシグネチャが無効です
+5093,入力パラメーターの型が無効です
+5094,出力パラメーターが無効です
+5095,strict モードでは、関数の 'arguments' プロパティを使用できません
+5096,検査可能なオブジェクトが必要です
+5097,引数を型 'char' に変換できませんでした
+5098,引数を型 'GUID' に変換できませんでした
+5099,IInspectable が必要です
+5100,オブジェクトを構造体に変換できませんでした: オブジェクトに必要なプロパティがありません
+5101,不明な種類
+5102,関数の呼び出しの引数が少なすぎます
+5103,構造化可能な種類ではありません
+5104,値を PropertyValue に変換できませんでした: PropertyValue でサポートされている種類ではありません
+5105,値を IInspectable に変換できませんでした: IInspectable でサポートされている種類ではありません
+5106,日付を Windows.Foundation.DateTime に変換できませんでした: 有効な範囲外の値です
+5107,値を Windows.Foundation.TimeSpan に変換できませんでした: 有効な範囲外の値です
+5108,既にリリースされている検査可能なオブジェクトへのアクセスが無効です
+5109,既にリリースされている検査可能なオブジェクトはリリースできません
+5110,'this' は必要な型ではありません
+5111,配列に指定された長さとサイズは無効です
+5112,メタデータ情報を取得中に予期しないエラーが発生しました
+5200,スタータスは 'エラー' ですが、getResults はエラーを返しませんでした
+5201,完了したハンドラーに渡されるステータス パラメーターが見つからないか無効です
+5202,完了したハンドラーに渡される送信者パラメーターが見つからないか無効です
+6000,無限大
+6001,負の無限大
+10438,オブジェクトは '%s' プロパティまたはメソッドをサポートしていません。
+10449,関数 '%s' の引数は必須です。
+15001,'%s' は数値ではありません。
+15002,'%s' は関数ではありません。
+15004,'%s' は配列可能なオブジェクトではありません。
+15005,'%s' は文字列ではありません。
+15006,'%s' は日付型のオブジェクトではありません。
+15007,'%s' は Null であるか、オブジェクトではありません。
+15008,'%s' に割り当てられません。
+15009,'%s' は定義されていません。
+15010,'%s' はブール型ではありません。
+15012,'%s' を削除できません。
+15013,'%s' は VBArray ではありません。
+15014,'%s' は JavaScript オブジェクトではありません。
+15015,'%s' は Enumerator オブジェクトではありません。
+15016,'%s' は Regular Expression オブジェクトではありません。
+15028,'%s' は Array または Arguments オブジェクトではありません。
+15031,'%s' は Array オブジェクトではありません。
+15036,このオブジェクトでは、プロパティ記述子の '%s'' 属性を 'True' に設定できません
+15037,このオブジェクトでは、プロパティ記述子の '%s'' 属性を 'False' に設定できません
+15039,const '%s' が再定義されています。
+15041,strict モードでは、'%s' で delete を呼び出すことはできません
+15047,未定義または NULL 参照のプロパティ '%s' は設定できません
+15048,未定義または NULL 参照のプロパティ '%s' は取得できません
+15049,未定義または NULL 参照のプロパティ '%s' は削除できません
+15050,プロパティ '%s' にアクセスできません: データ型 'VarDate' はユーザー定義プロパティをサポートしません。
+15051,プロパティ '%s' の値は Function オブジェクトではありません。
+15052,プロパティ '%s' の値は Null または未定義で、Fuction オブジェクトではありません。
+15054,%s: 'this' は Null または未定義です。
+15055,%s: 'this' はオブジェクトではありません。
+15056,%s: 'this' は Function オブジェクトではありません。
+15057,%s: 'this' は String オブジェクトではありません。
+15058,%s: 'this' は Boolean オブジェクトではありません。
+15059,%s: 'this' は Date オブジェクトではありません。
+15060,%s: 'this' は Number オブジェクトではありません。
+15061,%s: 'this' は VBArray オブジェクトではありません。
+15062,%s: 'this' は JavaScript オブジェクトではありません。
+15063,%s: 'this' は Enumerator オブジェクトではありません。
+15064,%s: 'this' は RegExp オブジェクトではありません。
+15065,%s: 引数は無効です。
+15066,%s: 引数はオブジェクトではありません。
+15067,%s: 引数は JavaScript オブジェクトではありません。
+15068,%s: 引数は Function オブジェクトではありません。
+15069,%s: 引数は VBArray オブジェクトではありません。
+15070,%s: 引数は Null または未定義です。
+15071,%s: 引数はオブジェクトと Null のどちらでもありません。
+15072,%s: 引数に有効な 'length' プロパティがありません。
+15073,%s: Array または Arguments オブジェクトを指定してください。
+15074,'%s' へのオペランドが無効です: オブジェクトを指定してください。
+15075,'%s' へのオペランドが無効です: 関数を指定してください。
+15076,プロパティ '%s' の記述子が無効です。
+15077,プロパティ '%s' を定義できません: オブジェクトは拡張可能ではありません。
+15078,構成可能ではないプロパティ '%s' は再定義できません。
+15079,書き込み可能ではないプロパティ '%s' は変更できません。
+15080,プロパティ '%s' を変更できません: 'length' は書き込み可能ではありません。
+15081,プロパティ '%s' を定義できません。
+15088,DataView メソッドの必須引数 %s が指定されていません
+15090,DataView コンストラクター引数 %s が無効です
+15091,関数 '%s' に無効なシグネチャがあるため、呼び出せません
+15092,プロパティ '%s' に無効なシグネチャがあるため、アクセスできません
+15093,既定のインターフェイスが Windows.Foundation.IPropertyValue のランタイム クラス %s は、入力パラメーターの型としてサポートされていません
+15094,ランタイム クラス名が %s の Windows.Foundation.IPropertyValue インターフェイスを持つオブジェクトは、出力パラメーターとしてサポートされていません
+15096,%s: 'this' は検査可能なオブジェクトではありません
+15097,%s: 引数を型 'char' に変換できませんでした
+15098,%s: 引数を型 'GUID' に変換できませんでした
+15099,%s: 戻り値を IInspectable に変換できませんでした
+15100,オブジェクトを構造体に変換できませんでした: オブジェクトに必要なプロパティ '%s' がありません
+15101,種類 '%s' が見つかりません
+15102,%s: 関数の呼び出しの引数が少なすぎます
+15103,%s: 構造化可能な種類ではありません
+15104,値を PropertyValue に変換できませんでした: %s は PropertyValue でサポートされていません
+15105,値を IInspectable に変換できませんでした: %s は IInspectable でサポートされていません
+15108,%s: 検査可能なオブジェクト 'this' はリリースされていてアクセスできません
+15110,'this' は次の必要な型ではありません: %s
+15112,%s: メタデータ情報を取得中に予期しないエラーが発生しました
+32812,指定された日は現在設定されているロケールのカレンダーでは使用できません。
diff --git a/src/sentry/data/error-locale/ko-KR.txt b/src/sentry/data/error-locale/ko-KR.txt
new file mode 100644
index 00000000000000..9dfa3fd8443ac1
--- /dev/null
+++ b/src/sentry/data/error-locale/ko-KR.txt
@@ -0,0 +1,289 @@
+5,프로시저 호출 또는 인수가 잘못되었습니다.
+6,오버플로
+7,메모리 부족
+9,첨자 사용이 잘못되었습니다.
+10,이 배열은 고정되었거나 일시적으로 잠금 상태입니다.
+11,0으로 나누기
+13,형식이 일치하지 않습니다.
+14,문자열 공간을 넘어갑니다.
+17,요청한 작업을 수행할 수 없습니다.
+28,스택 공간 부족
+35,Sub 또는 Function이 정의되지 않았습니다.
+48,DLL을 읽어들이는 데 오류 발생
+51,내부 오류
+52,파일 이름이나 숫자가 적합하지 않습니다.
+53,파일을 찾을 수 없습니다.
+54,비적합 파일 모드
+55,이미 열려 있는 파일입니다.
+57,장치 I/O 오류
+58,파일이 이미 존재합니다.
+61,디스크에 여유 공간이 없습니다.
+62,파일의 끝을 넘어가는 입력입니다.
+67,파일이 너무 많습니다.
+68,사용할 수 없는 장치입니다.
+70,사용 권한이 없습니다.
+71,디스크가 준비되지 않았습니다.
+74,드라이브 이름을 바꿀 수 없습니다.
+75,경로/파일 접근 오류
+76,경로를 찾을 수 없습니다.
+91,개체 변수 또는 With 문의 변수가 설정되어 있지 않습니다.
+92,For 루프를 초기화하지 않았습니다.
+94,Null 값의 사용이 잘못되었습니다.
+322,필요한 임시 파일을 만들 수 없습니다.
+424,개체가 필요합니다.
+429,자동화 서버는 개체를 작성할 수 없습니다.
+430,클래스가 자동화를 지원하지 않습니다.
+432,자동화 수행 중 파일 이름이나 클래스 이름을 찾을 수 없습니다.
+438,개체가 이 속성 또는 메서드를 지원하지 않습니다.
+440,자동화 오류
+445,개체는 이 기능을 지원하지 않습니다.
+446,개체는 지정된 인수를 지원하지 않습니다.
+447,개체는 현재의 로케일 설정을 지원하지 않습니다.
+448,지정된 인수를 찾을 수 없습니다.
+449,인수를 선택할 수 없습니다.
+450,인수의 개수나 속성 할당이 잘못되었습니다.
+451,컬렉션이 아닌 개체입니다.
+453,지정한 DLL 함수를 찾을 수 없습니다.
+458,변수에 JavaScript에서 지원하지 않는 자동화 형식이 사용되었습니다.
+462,원격 서버가 존재하지 않거나 사용할 수 없는 상태입니다.
+501,변수에 할당할 수 없습니다.
+502,스크립트에 안전하지 않은 개체입니다.
+503,초기화에 안전하지 않은 개체입니다.
+504,만들기에 안전하지 않은 개체입니다.
+507,예외 사항 발생
+1001,메모리 부족
+1002,구문 오류
+1003,':'가 필요합니다.
+1004,';'가 필요합니다.
+1005,'('가 필요합니다.
+1006,')'가 필요합니다.
+1007,']'가 필요합니다.
+1008,'{'가 필요합니다.
+1009,'}'가 필요합니다.
+1010,식별자가 필요합니다.
+1011,'='가 필요합니다.
+1012,'/'가 필요합니다.
+1013,유효하지 않은 숫자입니다.
+1014,유효하지 않은 문자입니다.
+1015,종결되지 않은 문자열 상수입니다.
+1016,종결되지 않은 주석입니다.
+1018,함수 밖의 'return' 문장
+1019,루프 밖에서는 'break'을 사용할 수 없습니다.
+1020,루프 밖에서는 'continue'를 사용할 수 없습니다.
+1023,16진수가 필요합니다.
+1024,'while'이 필요합니다.
+1025,레이블이 재정의되었습니다.
+1026,레이블을 찾을 수 없습니다.
+1027,'switch'문에서 'default'는 한번만 나타날 수 있습니다.
+1028,식별자나 문자열 또는 숫자가 필요합니다.
+1029,'@end'가 필요합니다.
+1030,조건부 컴파일이 해제되었습니다.
+1031,상수가 필요합니다.
+1032,'@'가 필요합니다.
+1033,'catch'가 필요합니다.
+1034,'var'가 필요합니다.
+1035,수식이 'throw'와 같은 줄에 반드시 뒤따라야 합니다.
+1037,엄격 모드에서는 'with' 문을 사용할 수 없습니다.
+1038,엄격 모드에서는 형식 매개 변수 이름을 중복하여 사용할 수 없습니다.
+1039,엄격 모드에서는 8진수 리터럴과 이스케이프 문자를 사용할 수 없습니다.
+1041,엄격 모드에서 'eval' 사용 방법이 잘못되었습니다.
+1042,엄격 모드에서 'arguments' 사용 방법이 잘못되었습니다.
+1045,엄격 모드에서는 식에서 delete를 호출할 수 없습니다.
+1046,엄격 모드에서는 속성을 여러 번 정의할 수 없습니다.
+1047,strict 모드에서는 문 또는 블록 안에 함수 선언을 중첩할 수 없습니다. 최상위 수준이나 함수 본문 바로 안에만 표시될 수 있습니다.
+1048,식별자에 대한 키워드 사용이 잘못되었습니다.
+1049,식별자에 대한 미래 예약어 사용이 잘못되었습니다.
+1050,식별자에 대한 미래 예약어 사용이 잘못되었습니다. 식별자 이름이 strict 모드에서 예약되었습니다.
+1051,Setter 함수에는 하나의 인수가 있어야 합니다.
+4096,JavaScript 컴파일 오류
+4097,JavaScript 런타임 오류
+4098,알 수 없는 런타임 오류
+5000,'this'에 할당할 수 없습니다.
+5001,숫자가 필요합니다.
+5002,함수가 필요합니다.
+5003,함수 결과에 할당할 수 없습니다.
+5004,개체를 인덱스할 수 없습니다.
+5005,문자열이 필요합니다.
+5006,날짜 개체가 필요합니다.
+5007,개체가 필요합니다.
+5008,왼쪽에 할당된 값이 잘못되었습니다.
+5009,정의되지 않은 식별자
+5010,Boolean이 필요합니다.
+5011,삭제된 스크립트의 코드는 수행할 수 없습니다.
+5012,개체 구성원이 필요합니다.
+5013,VBArray가 필요합니다.
+5014,JavaScript 개체가 필요합니다.
+5015,열거형 개체가 필요합니다.
+5016,정규식 개체가 필요합니다.
+5017,정규식에 구문 오류가 있습니다.
+5018,예기치 않은 수량사
+5019,정규식에 ']'가 필요합니다.
+5020,정규식에 ')'가 필요합니다.
+5021,잘못된 범주의 문자 집합입니다.
+5022,예외가 발생했지만 catch할 수 없습니다.
+5023,함수에 유효한 표준 개체가 없습니다.
+5024,인코딩될 URI가 유효하지 않은 문자를 포함하고 있습니다.
+5025,디코딩될 URI가 유효한 인코딩이 아닙니다.
+5026,소수부 자리수가 범위를 초과하였습니다.
+5027,정밀도가 범위를 초과하였습니다.
+5028,Array 또는 arguments 개체가 필요합니다.
+5029,배열의 길이는 유한한 양의 정수이어야 합니다.
+5030,배열의 길이는 유한한 양수로 할당해야 합니다.
+5031,배열 개체가 필요합니다.
+5034,값 인수에 순환 참조를 사용하는 것은 지원되지 않습니다.
+5035,치환 인수가 잘못되었습니다.
+5038,인수 목록이 너무 커서 적용할 수 없습니다.
+5039,const 속성 다시 선언
+5041,개체 구성원을 구성할 수 없습니다.
+5042,엄격 모드에서 변수가 정의되지 않았습니다.
+5043,strict 모드에서는 함수 또는 인수 개체의 'caller' 속성에 액세스할 수 없습니다.
+5044,strict 모드에서는 인수 개체의 'callee' 속성에 액세스할 수 없습니다.
+5045,strict 모드에서는 읽기 전용 속성에 할당할 수 없습니다.
+5046,확장 가능하지 않은 개체에 대해 속성을 만들 수 없습니다.
+5047,개체가 필요합니다.
+5048,개체가 필요합니다.
+5049,개체가 필요합니다.
+5050,개체가 필요합니다.
+5051,함수가 필요합니다.
+5052,함수가 필요합니다.
+5053,속성에 접근자와 값을 둘 다 지정할 수는 없습니다.
+5054,'this'가 null이거나 정의되지 않았습니다.
+5055,개체가 필요합니다.
+5056,함수가 필요합니다.
+5057,문자열이 필요합니다.
+5058,Boolean이 필요합니다.
+5059,날짜가 필요합니다.
+5060,숫자가 필요합니다.
+5061,VBArray가 필요합니다.
+5062,JavaScript 개체가 필요합니다.
+5063,열거형 개체가 필요합니다.
+5064,RegExp 개체가 필요합니다.
+5065,함수 인수가 잘못되었습니다.
+5066,개체가 필요합니다.
+5067,JavaScript 개체가 필요합니다.
+5068,함수가 필요합니다.
+5069,VBArray가 필요합니다.
+5070,개체가 필요합니다.
+5071,개체가 필요합니다.
+5072,'length' 속성이 잘못되었습니다.
+5073,Array 또는 arguments 개체가 필요합니다.
+5074,피연산자가 잘못되었습니다.
+5075,피연산자가 잘못되었습니다.
+5076,속성 설명자가 잘못되었습니다.
+5077,속성을 정의할 수 없습니다. 개체가 확장 가능하지 않습니다.
+5078,구성 가능하지 않은 속성을 재정의할 수 없습니다.
+5079,쓰기 가능하지 않은 속성을 수정할 수 없습니다.
+5080,속성을 수정할 수 없습니다. 'length'를 쓸 수 없습니다.
+5081,속성을 정의할 수 없습니다.
+5082,형식화된 배열 생성자 인수가 잘못되었습니다.
+5083,'this'가 형식화된 배열 개체가 아닙니다.
+5084,형식화된 배열을 만들 때의 오프셋/길이가 잘못되었습니다.
+5085,형식화된 배열의 하위 배열 메서드에 잘못된 시작/끝 값이 있습니다.
+5086,형식화된 배열 집합에 잘못된 원본이 있습니다.
+5087,'this'가 DataView 개체가 아닙니다.
+5088,DataView에 잘못된 인수가 있습니다.
+5089,DataView 작업이 지정된 버퍼 길이를 초과하여 액세스합니다.
+5090,DataView에 잘못된 인수가 있습니다.
+5091,잘못된 함수 서명
+5092,잘못된 속성 서명
+5093,잘못된 입력 매개 변수 형식
+5094,잘못된 출력 매개 변수
+5095,strict 모드에서는 함수의 'arguments' 속성에 액세스할 수 없습니다.
+5096,Inspectable 개체가 필요합니다.
+5097,'char' 유형으로 인수를 변환할 수 없습니다.
+5098,'GUID' 유형으로 인수를 변환할 수 없습니다.
+5099,IInspectable이 필요합니다.
+5100,개체를 구조체로 변환할 수 없습니다. 개체에 필요한 속성이 누락되었습니다.
+5101,알 수 없는 형식
+5102,너무 적은 인수로 호출된 함수입니다.
+5103,유형은 구성할 수 없습니다.
+5104,값을 PropertyValue로 변환할 수 없습니다. PropertyValue에서 지원하지 않는 유형입니다.
+5105,값을 IInspectable로 변환할 수 없습니다. IInspectable에서 지원하지 않는 유형입니다.
+5106,날짜를 Windows.Foundation.DateTime으로 변환할 수 없습니다. 유효한 범위에 있지 않은 값입니다.
+5107,값을 Windows.Foundation.TimeSpan으로 변환할 수 없습니다. 유효한 범위를 벗어난 값입니다.
+5108,이미 릴리스된 Inspectable 개체에 대한 잘못된 액세스입니다.
+5109,이미 릴리스된 Inspectable 개체는 릴리스할 수 없습니다.
+5110,'이것'은 예상 형식이 아님
+5111,배열에 지정된 길이 및 크기가 잘못되었습니다.
+5112,메타데이터 정보를 가져오는 동안 예기치 않은 오류가 발생했습니다.
+5200,상태는 '오류'지만, getResults에서 오류를 반환하지 않았습니다.
+5201,완료된 처리기에 누락되거나 잘못된 상태 매개 변수가 전달됨
+5202,완료된 처리기에 누락되거나 잘못된 상태 매개 변수가 전달됨
+6000,무한대
+6001,-무한대
+10438,개체가 '%s' 속성이나 메서드를 지원하지 않습니다.
+10449,'%s' 함수에는 인수가 필요합니다.
+15001,'%s'은(는) 숫자가 아닙니다.
+15002,'%s'은(는) 함수가 아닙니다.
+15004,'%s'은(는) 인덱스를 지정할 수 있는 개체가 아닙니다.
+15005,'%s'은(는) 문자열이 아닙니다.
+15006,'%s'은(는) 날짜 개체가 아닙니다.
+15007,'%s'은(는) null이거나 개체가 아닙니다.
+15008,'%s'에 할당할 수 없습니다.
+15009,'%s'이(가) 정의되지 않았습니다.
+15010,'%s'은(는) boolean이 아닙니다.
+15012,'%s'을(를) 삭제할 수 없습니다.
+15013,'%s'은(는) VBArray가 아닙니다.
+15014,'%s'은(는) JavaScript 개체가 아닙니다.
+15015,'%s'은(는) 열거형 개체가 아닙니다.
+15016,'%s'은(는) 정규식 개체가 아닙니다.
+15028,%s은(는] Array 또는 arguments 개체가 아닙니다.
+15031,%s은(는) 배열(Array) 개체가 아닙니다.
+15036,속성 설명자의 '%s' 특성은 이 개체에 대해 'true'로 설정할 수 없습니다.
+15037,속성 설명자의 '%s' 특성은 이 개체에 대해 'false'로 설정할 수 없습니다.
+15039,const '%s' 다시 선언
+15041,엄격 모드에서는 '%s'에서 delete를 호출할 수 없습니다.
+15047,정의되지 않음 또는 null 참조인 '%s' 속성을 설정할 수 없습니다.
+15048,정의되지 않음 또는 null 참조인 '%s' 속성을 가져올 수 없습니다.
+15049,정의되지 않음 또는 null 참조인 '%s' 속성을 삭제할 수 없습니다.
+15050,'%s' 속성 값에 액세스할 수 없습니다. 'VarDate' 형식이 사용자 정의 속성을 지원하지 않습니다.
+15051,'%s' 속성 값이 Function 개체가 아닙니다.
+15052,'%s' 속성 값이 null이거나 정의되지 않았습니다. Function 개체가 아닙니다.
+15054,%s: 'this'가 null이거나 정의되지 않았습니다.
+15055,%s: 'this'가 Object가 아닙니다.
+15056,%s: 'this'가 Function 개체가 아닙니다.
+15057,%s: 'this'가 String 개체가 아닙니다.
+15058,%s: 'this'가 Boolean 개체가 아닙니다.
+15059,%s: 'this'가 Date 개체가 아닙니다.
+15060,%s: 'this'가 Number 개체가 아닙니다.
+15061,%s: 'this'가 VBArray 개체가 아닙니다.
+15062,%s: 'this'가 JavaScript 개체가 아닙니다.
+15063,%s: 'this'가 Enumerator 개체가 아닙니다.
+15064,%s: 'this'가 RegExp 개체가 아닙니다.
+15065,%s: 인수가 잘못되었습니다.
+15066,%s: 인수가 Object가 아닙니다.
+15067,%s: 인수가 JavaScript 개체가 아닙니다.
+15068,%s: 인수가 Function 개체가 아닙니다.
+15069,%s: 인수가 VBArray 개체가 아닙니다.
+15070,%s: 인수가 null이거나 정의되지 않았습니다.
+15071,%s: 인수가 Object가 아니고 null도 아닙니다.
+15072,%s: 인수에 올바른 'length' 속성이 없습니다.
+15073,%s: 배열 또는 인수 개체가 필요합니다.
+15074,'%s'에 대한 피연산자가 잘못되었습니다. 개체가 필요합니다.
+15075,'%s'에 대한 피연산자가 잘못되었습니다. 함수가 필요합니다.
+15076,'%s' 속성에 대한 설명자가 잘못되었습니다.
+15077,'%s' 속성을 정의할 수 없습니다. 개체가 확장 가능하지 않습니다.
+15078,구성 가능하지 않은 속성 '%s'을(를) 재정의할 수 없습니다.
+15079,구성 가능하지 않은 속성 '%s'을(를) 수정할 수 없습니다.
+15080,'%s' 속성을 수정할 수 없습니다. 'length'를 쓸 수 없습니다.
+15081,'%s' 속성을 정의할 수 없습니다.
+15088,DataView 메서드의 필수 인수 %s을(를) 지정하지 않았습니다.
+15090,DataView 생성자 인수 %s이(가) 잘못되었습니다.
+15091,'%s' 함수에 잘못된 서명이 있으며 호출할 수 없습니다.
+15092,'%s' 속성에 잘못된 서명이 있으며 액세스할 수 없습니다.
+15093,Windows.Foundation.IPropertyValue를 기본 인터페이스로 사용하는 runtimeclass %s은(는) 입력 매개 변수 형식으로 지원되지 않습니다.
+15094,runtimeclass 이름이 %s인 인터페이스 Windows.Foundation.IPropertyValue를 사용하는 개체는 출력 매개 변수로 지원되지 않습니다.
+15096,%s: 'this'는 Inspectable 개체가 아닙니다.
+15097,%s: 인수를 'char' 유형으로 변환할 수 없습니다.
+15098,%s: 인수를 'GUID' 유형으로 변환할 수 없습니다.
+15099,%s: 반환 값을 IInspectable로 변환할 수 없습니다.
+15100,개체를 구조체로 변환할 수 없습니다. 개체에 필요한 속성 '%s'이(가) 누락되었습니다.
+15101,'%s' 유형을 찾지 못했습니다.
+15102,%s: 너무 적은 인수로 호출된 함수입니다.
+15103,%s: 유형을 구성할 수 없습니다.
+15104,값을 PropertyValue로 변환할 수 없습니다. %s은(는) PropertyValue에서 지원되지 않습니다.
+15105,값을 IInspectable로 변환할 수 없습니다. %s은(는) IInspectable에서 지원되지 않습니다.
+15108,%s: Inspectable 개체 'this'가 릴리스되었으며 액세스할 수 없습니다.
+15110,'이것'은 예상 형식이 아님: %s
+15112,%s: 메타데이터 정보를 가져오는 동안 예기치 않은 오류가 발생했습니다.
+32812,현재 로케일의 달력에서는 지정한 날짜를 사용할 수 없습니다.
diff --git a/src/sentry/data/error-locale/lt-LT.txt b/src/sentry/data/error-locale/lt-LT.txt
new file mode 100644
index 00000000000000..6580983d9b4a88
--- /dev/null
+++ b/src/sentry/data/error-locale/lt-LT.txt
@@ -0,0 +1,289 @@
+5,Invalid procedure call or argument
+6,Overflow
+7,Out of memory
+9,Subscript out of range
+10,This array is fixed or temporarily locked
+11,Division by zero
+13,Type mismatch
+14,Out of string space
+17,Can't perform requested operation
+28,Out of stack space
+35,Sub or Function not defined
+48,Error in loading DLL
+51,Internal error
+52,Bad file name or number
+53,File not found
+54,Bad file mode
+55,File already open
+57,Device I/O error
+58,File already exists
+61,Disk full
+62,Input past end of file
+67,Too many files
+68,Device unavailable
+70,Permission denied
+71,Disk not ready
+74,Can't rename with different drive
+75,Path/File access error
+76,Path not found
+91,Object variable or With block variable not set
+92,For loop not initialized
+94,Invalid use of Null
+322,Can't create necessary temporary file
+424,Object required
+429,Automation server can't create object
+430,Class doesn't support Automation
+432,File name or class name not found during Automation operation
+438,Object doesn't support this property or method
+440,Automation error
+445,Object doesn't support this action
+446,Object doesn't support named arguments
+447,Object doesn't support current locale setting
+448,Named argument not found
+449,Argument not optional
+450,Wrong number of arguments or invalid property assignment
+451,Object not a collection
+453,Specified DLL function not found
+458,Variable uses an Automation type not supported in JavaScript
+462,The remote server machine does not exist or is unavailable
+501,Cannot assign to variable
+502,Object not safe for scripting
+503,Object not safe for initializing
+504,Object not safe for creating
+507,An exception occurred
+1001,Out of memory
+1002,Syntax error
+1003,Expected ':'
+1004,Expected ';'
+1005,Expected '('
+1006,Expected ')'
+1007,Expected ']'
+1008,Expected '{'
+1009,Expected '}'
+1010,Expected identifier
+1011,Expected '='
+1012,Expected '/'
+1013,Invalid number
+1014,Invalid character
+1015,Unterminated string constant
+1016,Unterminated comment
+1018,'return' statement outside of function
+1019,Can't have 'break' outside of loop
+1020,Can't have 'continue' outside of loop
+1023,Expected hexadecimal digit
+1024,Expected 'while'
+1025,Label redefined
+1026,Label not found
+1027,'default' can only appear once in a 'switch' statement
+1028,Expected identifier, string or number
+1029,Expected '@end'
+1030,Conditional compilation is turned off
+1031,Expected constant
+1032,Expected '@'
+1033,Expected 'catch'
+1034,Expected 'var'
+1035,'throw' must be followed by an expression on the same source line
+1037,'with' statements are not allowed in strict mode
+1038,Duplicate formal parameter names not allowed in strict mode
+1039,Octal numeric literals and escape characters not allowed in strict mode
+1041,Invalid usage of 'eval' in strict mode
+1042,Invalid usage of 'arguments' in strict mode
+1045,Calling delete on expression not allowed in strict mode
+1046,Multiple definitions of a property not allowed in strict mode
+1047,In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.
+1048,The use of a keyword for an identifier is invalid
+1049,The use of a future reserved word for an identifier is invalid
+1050,The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.
+1051,Setter functions must have one argument
+4096,JavaScript compilation error
+4097,JavaScript runtime error
+4098,Unknown runtime error
+5000,Cannot assign to 'this'
+5001,Number expected
+5002,Function expected
+5003,Cannot assign to a function result
+5004,Cannot index object
+5005,String expected
+5006,Date object expected
+5007,Object expected
+5008,Invalid left-hand side in assignment
+5009,Undefined identifier
+5010,Boolean expected
+5011,Can't execute code from a freed script
+5012,Object member expected
+5013,VBArray expected
+5014,JavaScript object expected
+5015,Enumerator object expected
+5016,Regular Expression object expected
+5017,Syntax error in regular expression
+5018,Unexpected quantifier
+5019,Expected ']' in regular expression
+5020,Expected ')' in regular expression
+5021,Invalid range in character set
+5022,Exception thrown and not caught
+5023,Function does not have a valid prototype object
+5024,The URI to be encoded contains an invalid character
+5025,The URI to be decoded is not a valid encoding
+5026,The number of fractional digits is out of range
+5027,The precision is out of range
+5028,Array or arguments object expected
+5029,Array length must be a finite positive integer
+5030,Array length must be assigned a finite positive number
+5031,Array object expected
+5034,Circular reference in value argument not supported
+5035,Invalid replacer argument
+5038,Argument list too large to apply
+5039,Redeclaration of const property
+5041,Object member not configurable
+5042,Variable undefined in strict mode
+5043,Accessing the 'caller' property of a function or arguments object is not allowed in strict mode
+5044,Accessing the 'callee' property of an arguments object is not allowed in strict mode
+5045,Assignment to read-only properties is not allowed in strict mode
+5046,Cannot create property for a non-extensible object
+5047,Object expected
+5048,Object expected
+5049,Object expected
+5050,Object expected
+5051,Function expected
+5052,Function expected
+5053,Property cannot have both accessors and a value
+5054,'this' is null or undefined
+5055,Object expected
+5056,Function expected
+5057,String expected
+5058,Boolean expected
+5059,Date expected
+5060,Number expected
+5061,VBArray expected
+5062,JavaScript object expected
+5063,Enumerator object expected
+5064,RegExp object expected
+5065,Invalid function argument
+5066,Object expected
+5067,JavaScript object expected
+5068,Function expected
+5069,VBArray expected
+5070,Object expected
+5071,Object expected
+5072,Invalid 'length' property
+5073,Array or arguments object expected
+5074,Invalid Operand
+5075,Invalid Operand
+5076,Invalid property descriptor
+5077,Cannot define property: object is not extensible
+5078,Cannot redefine non-configurable property
+5079,Cannot modify non-writable property
+5080,Cannot modify property: 'length' is not writable
+5081,Cannot define property
+5082,Typed array constructor argument is invalid
+5083,'this' is not a typed array object
+5084,Invalid offset/length when creating typed array
+5085,Invalid begin/end value in typed array subarray method
+5086,Invalid source in typed array set
+5087,'this' is not a DataView object
+5088,Invalid arguments in DataView
+5089,DataView operation access beyond specified buffer length
+5090,Invalid arguments in DataView
+5091,invalid function signature
+5092,invalid property signature
+5093,invalid input parameter type
+5094,invalid ouput parameter
+5095,Accessing the 'arguments' property of a function is not allowed in strict mode
+5096,Inspectable Object expected
+5097,Could not convert argument to type 'char'
+5098,Could not convert argument to type 'GUID'
+5099,IInspectable expected
+5100,Could not convert object to struct: object missing expected property
+5101,Unknown type
+5102,Function called with too few arguments
+5103,Type is not constructible
+5104,Could not convert value to PropertyValue: Type not supported by PropertyValue
+5105,Could not convert value to IInspectable: Type not supported by IInspectable
+5106,Could not convert Date to Windows.Foundation.DateTime: value outside of valid range
+5107,Could not convert value to Windows.Foundation.TimeSpan: value outside of valid range
+5108,Invalid access to already released Inspectable Object
+5109,Cannot release already released Inspectable Object
+5110,'this' is not of the expected type
+5111,Illegal length and size specified for the array
+5112,An unexpected failure occurred while trying to obtain metadata information
+5200,Status is 'error', but getResults did not return an error
+5201,Missing or invalid status parameter passed to completed handler
+5202,Missing or invalid sender parameter passed to completed handler
+6000,Infinity
+6001,-Infinity
+10438,Object doesn't support property or method '%s'
+10449,Argument to the function '%s' is not optional
+15001,'%s' is not a number
+15002,'%s' is not a function
+15004,'%s' is not an indexable object
+15005,'%s' is not a string
+15006,'%s' is not a date object
+15007,'%s' is null or not an object
+15008,Cannot assign to '%s'
+15009,'%s' is undefined
+15010,'%s' is not a boolean
+15012,Cannot delete '%s'
+15013,'%s' is not a VBArray
+15014,'%s' is not a JavaScript object
+15015,'%s' is not an enumerator object
+15016,'%s' is not a regular expression object
+15028,%s is not an Array or arguments object
+15031,%s is not an Array object
+15036,'%s' attribute on the property descriptor cannot be set to 'true' on this object
+15037,'%s' attribute on the property descriptor cannot be set to 'false' on this object
+15039,Redeclaration of const '%s'
+15041,Calling delete on '%s' is not allowed in strict mode
+15047,Unable to set property '%s' of undefined or null reference
+15048,Unable to get property '%s' of undefined or null reference
+15049,Unable to delete property '%s' of undefined or null reference
+15050,Unable to access property '%s': type 'VarDate' does not support user-defined properties
+15051,The value of the property '%s' is not a Function object
+15052,The value of the property '%s' is null or undefined, not a Function object
+15054,%s: 'this' is null or undefined
+15055,%s: 'this' is not an Object
+15056,%s: 'this' is not a Function object
+15057,%s: 'this' is not a String object
+15058,%s: 'this' is not a Boolean object
+15059,%s: 'this' is not a Date object
+15060,%s: 'this' is not a Number object
+15061,%s: 'this' is not a VBArray object
+15062,%s: 'this' is not a JavaScript object
+15063,%s: 'this' is not an Enumerator object
+15064,%s: 'this' is not a RegExp object
+15065,%s: invalid argument
+15066,%s: argument is not an Object
+15067,%s: argument is not a JavaScript object
+15068,%s: argument is not a Function object
+15069,%s: argument is not a VBArray object
+15070,%s: argument is null or undefined
+15071,%s: argument is not an Object and is not null
+15072,%s: argument does not have a valid 'length' property
+15073,%s: Array or arguments object expected
+15074,Invalid operand to '%s': Object expected
+15075,Invalid operand to '%s': Function expected
+15076,Invalid descriptor for property '%s'
+15077,Cannot define property '%s': object is not extensible
+15078,Cannot redefine non-configurable property '%s'
+15079,Cannot modify non-writable property '%s'
+15080,Cannot modify property '%s': 'length' is not writable
+15081,Cannot define property '%s'
+15088,Required argument %s in DataView method is not specified
+15090,DataView constructor argument %s is invalid
+15091,The function '%s' has an invalid signature and cannot be called
+15092,The property '%s' has an invalid signature and cannot be accessed
+15093,The runtimeclass %s that has Windows.Foundation.IPropertyValue as default interface is not supported as input parameter type
+15094,The object with interface Windows.Foundation.IPropertyValue that has runtimeclass name %s is not supported as out parameter
+15096,%s: 'this' is not an Inspectable Object
+15097,%s: could not convert argument to type 'char'
+15098,%s: could not convert argument to type 'GUID'
+15099,%s: could not convert return value to IInspectable
+15100,Could not convert object to struct: object missing expected property '%s'
+15101,Type '%s' not found
+15102,%s: function called with too few arguments
+15103,%s: type is not constructible
+15104,Could not convert value to PropertyValue: %s not supported by PropertyValue
+15105,Could not convert value to IInspectable: %s not supported by IInspectable
+15108,%s: The Inspectable object 'this' is released and cannot be accessed
+15110,'this' is not of expected type: %s
+15112,%s: an unexpected failure occurred while trying to obtain metadata information
+32812,The specified date is not available in the current locale's calendar
diff --git a/src/sentry/data/error-locale/lv-LV.txt b/src/sentry/data/error-locale/lv-LV.txt
new file mode 100644
index 00000000000000..6580983d9b4a88
--- /dev/null
+++ b/src/sentry/data/error-locale/lv-LV.txt
@@ -0,0 +1,289 @@
+5,Invalid procedure call or argument
+6,Overflow
+7,Out of memory
+9,Subscript out of range
+10,This array is fixed or temporarily locked
+11,Division by zero
+13,Type mismatch
+14,Out of string space
+17,Can't perform requested operation
+28,Out of stack space
+35,Sub or Function not defined
+48,Error in loading DLL
+51,Internal error
+52,Bad file name or number
+53,File not found
+54,Bad file mode
+55,File already open
+57,Device I/O error
+58,File already exists
+61,Disk full
+62,Input past end of file
+67,Too many files
+68,Device unavailable
+70,Permission denied
+71,Disk not ready
+74,Can't rename with different drive
+75,Path/File access error
+76,Path not found
+91,Object variable or With block variable not set
+92,For loop not initialized
+94,Invalid use of Null
+322,Can't create necessary temporary file
+424,Object required
+429,Automation server can't create object
+430,Class doesn't support Automation
+432,File name or class name not found during Automation operation
+438,Object doesn't support this property or method
+440,Automation error
+445,Object doesn't support this action
+446,Object doesn't support named arguments
+447,Object doesn't support current locale setting
+448,Named argument not found
+449,Argument not optional
+450,Wrong number of arguments or invalid property assignment
+451,Object not a collection
+453,Specified DLL function not found
+458,Variable uses an Automation type not supported in JavaScript
+462,The remote server machine does not exist or is unavailable
+501,Cannot assign to variable
+502,Object not safe for scripting
+503,Object not safe for initializing
+504,Object not safe for creating
+507,An exception occurred
+1001,Out of memory
+1002,Syntax error
+1003,Expected ':'
+1004,Expected ';'
+1005,Expected '('
+1006,Expected ')'
+1007,Expected ']'
+1008,Expected '{'
+1009,Expected '}'
+1010,Expected identifier
+1011,Expected '='
+1012,Expected '/'
+1013,Invalid number
+1014,Invalid character
+1015,Unterminated string constant
+1016,Unterminated comment
+1018,'return' statement outside of function
+1019,Can't have 'break' outside of loop
+1020,Can't have 'continue' outside of loop
+1023,Expected hexadecimal digit
+1024,Expected 'while'
+1025,Label redefined
+1026,Label not found
+1027,'default' can only appear once in a 'switch' statement
+1028,Expected identifier, string or number
+1029,Expected '@end'
+1030,Conditional compilation is turned off
+1031,Expected constant
+1032,Expected '@'
+1033,Expected 'catch'
+1034,Expected 'var'
+1035,'throw' must be followed by an expression on the same source line
+1037,'with' statements are not allowed in strict mode
+1038,Duplicate formal parameter names not allowed in strict mode
+1039,Octal numeric literals and escape characters not allowed in strict mode
+1041,Invalid usage of 'eval' in strict mode
+1042,Invalid usage of 'arguments' in strict mode
+1045,Calling delete on expression not allowed in strict mode
+1046,Multiple definitions of a property not allowed in strict mode
+1047,In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.
+1048,The use of a keyword for an identifier is invalid
+1049,The use of a future reserved word for an identifier is invalid
+1050,The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.
+1051,Setter functions must have one argument
+4096,JavaScript compilation error
+4097,JavaScript runtime error
+4098,Unknown runtime error
+5000,Cannot assign to 'this'
+5001,Number expected
+5002,Function expected
+5003,Cannot assign to a function result
+5004,Cannot index object
+5005,String expected
+5006,Date object expected
+5007,Object expected
+5008,Invalid left-hand side in assignment
+5009,Undefined identifier
+5010,Boolean expected
+5011,Can't execute code from a freed script
+5012,Object member expected
+5013,VBArray expected
+5014,JavaScript object expected
+5015,Enumerator object expected
+5016,Regular Expression object expected
+5017,Syntax error in regular expression
+5018,Unexpected quantifier
+5019,Expected ']' in regular expression
+5020,Expected ')' in regular expression
+5021,Invalid range in character set
+5022,Exception thrown and not caught
+5023,Function does not have a valid prototype object
+5024,The URI to be encoded contains an invalid character
+5025,The URI to be decoded is not a valid encoding
+5026,The number of fractional digits is out of range
+5027,The precision is out of range
+5028,Array or arguments object expected
+5029,Array length must be a finite positive integer
+5030,Array length must be assigned a finite positive number
+5031,Array object expected
+5034,Circular reference in value argument not supported
+5035,Invalid replacer argument
+5038,Argument list too large to apply
+5039,Redeclaration of const property
+5041,Object member not configurable
+5042,Variable undefined in strict mode
+5043,Accessing the 'caller' property of a function or arguments object is not allowed in strict mode
+5044,Accessing the 'callee' property of an arguments object is not allowed in strict mode
+5045,Assignment to read-only properties is not allowed in strict mode
+5046,Cannot create property for a non-extensible object
+5047,Object expected
+5048,Object expected
+5049,Object expected
+5050,Object expected
+5051,Function expected
+5052,Function expected
+5053,Property cannot have both accessors and a value
+5054,'this' is null or undefined
+5055,Object expected
+5056,Function expected
+5057,String expected
+5058,Boolean expected
+5059,Date expected
+5060,Number expected
+5061,VBArray expected
+5062,JavaScript object expected
+5063,Enumerator object expected
+5064,RegExp object expected
+5065,Invalid function argument
+5066,Object expected
+5067,JavaScript object expected
+5068,Function expected
+5069,VBArray expected
+5070,Object expected
+5071,Object expected
+5072,Invalid 'length' property
+5073,Array or arguments object expected
+5074,Invalid Operand
+5075,Invalid Operand
+5076,Invalid property descriptor
+5077,Cannot define property: object is not extensible
+5078,Cannot redefine non-configurable property
+5079,Cannot modify non-writable property
+5080,Cannot modify property: 'length' is not writable
+5081,Cannot define property
+5082,Typed array constructor argument is invalid
+5083,'this' is not a typed array object
+5084,Invalid offset/length when creating typed array
+5085,Invalid begin/end value in typed array subarray method
+5086,Invalid source in typed array set
+5087,'this' is not a DataView object
+5088,Invalid arguments in DataView
+5089,DataView operation access beyond specified buffer length
+5090,Invalid arguments in DataView
+5091,invalid function signature
+5092,invalid property signature
+5093,invalid input parameter type
+5094,invalid ouput parameter
+5095,Accessing the 'arguments' property of a function is not allowed in strict mode
+5096,Inspectable Object expected
+5097,Could not convert argument to type 'char'
+5098,Could not convert argument to type 'GUID'
+5099,IInspectable expected
+5100,Could not convert object to struct: object missing expected property
+5101,Unknown type
+5102,Function called with too few arguments
+5103,Type is not constructible
+5104,Could not convert value to PropertyValue: Type not supported by PropertyValue
+5105,Could not convert value to IInspectable: Type not supported by IInspectable
+5106,Could not convert Date to Windows.Foundation.DateTime: value outside of valid range
+5107,Could not convert value to Windows.Foundation.TimeSpan: value outside of valid range
+5108,Invalid access to already released Inspectable Object
+5109,Cannot release already released Inspectable Object
+5110,'this' is not of the expected type
+5111,Illegal length and size specified for the array
+5112,An unexpected failure occurred while trying to obtain metadata information
+5200,Status is 'error', but getResults did not return an error
+5201,Missing or invalid status parameter passed to completed handler
+5202,Missing or invalid sender parameter passed to completed handler
+6000,Infinity
+6001,-Infinity
+10438,Object doesn't support property or method '%s'
+10449,Argument to the function '%s' is not optional
+15001,'%s' is not a number
+15002,'%s' is not a function
+15004,'%s' is not an indexable object
+15005,'%s' is not a string
+15006,'%s' is not a date object
+15007,'%s' is null or not an object
+15008,Cannot assign to '%s'
+15009,'%s' is undefined
+15010,'%s' is not a boolean
+15012,Cannot delete '%s'
+15013,'%s' is not a VBArray
+15014,'%s' is not a JavaScript object
+15015,'%s' is not an enumerator object
+15016,'%s' is not a regular expression object
+15028,%s is not an Array or arguments object
+15031,%s is not an Array object
+15036,'%s' attribute on the property descriptor cannot be set to 'true' on this object
+15037,'%s' attribute on the property descriptor cannot be set to 'false' on this object
+15039,Redeclaration of const '%s'
+15041,Calling delete on '%s' is not allowed in strict mode
+15047,Unable to set property '%s' of undefined or null reference
+15048,Unable to get property '%s' of undefined or null reference
+15049,Unable to delete property '%s' of undefined or null reference
+15050,Unable to access property '%s': type 'VarDate' does not support user-defined properties
+15051,The value of the property '%s' is not a Function object
+15052,The value of the property '%s' is null or undefined, not a Function object
+15054,%s: 'this' is null or undefined
+15055,%s: 'this' is not an Object
+15056,%s: 'this' is not a Function object
+15057,%s: 'this' is not a String object
+15058,%s: 'this' is not a Boolean object
+15059,%s: 'this' is not a Date object
+15060,%s: 'this' is not a Number object
+15061,%s: 'this' is not a VBArray object
+15062,%s: 'this' is not a JavaScript object
+15063,%s: 'this' is not an Enumerator object
+15064,%s: 'this' is not a RegExp object
+15065,%s: invalid argument
+15066,%s: argument is not an Object
+15067,%s: argument is not a JavaScript object
+15068,%s: argument is not a Function object
+15069,%s: argument is not a VBArray object
+15070,%s: argument is null or undefined
+15071,%s: argument is not an Object and is not null
+15072,%s: argument does not have a valid 'length' property
+15073,%s: Array or arguments object expected
+15074,Invalid operand to '%s': Object expected
+15075,Invalid operand to '%s': Function expected
+15076,Invalid descriptor for property '%s'
+15077,Cannot define property '%s': object is not extensible
+15078,Cannot redefine non-configurable property '%s'
+15079,Cannot modify non-writable property '%s'
+15080,Cannot modify property '%s': 'length' is not writable
+15081,Cannot define property '%s'
+15088,Required argument %s in DataView method is not specified
+15090,DataView constructor argument %s is invalid
+15091,The function '%s' has an invalid signature and cannot be called
+15092,The property '%s' has an invalid signature and cannot be accessed
+15093,The runtimeclass %s that has Windows.Foundation.IPropertyValue as default interface is not supported as input parameter type
+15094,The object with interface Windows.Foundation.IPropertyValue that has runtimeclass name %s is not supported as out parameter
+15096,%s: 'this' is not an Inspectable Object
+15097,%s: could not convert argument to type 'char'
+15098,%s: could not convert argument to type 'GUID'
+15099,%s: could not convert return value to IInspectable
+15100,Could not convert object to struct: object missing expected property '%s'
+15101,Type '%s' not found
+15102,%s: function called with too few arguments
+15103,%s: type is not constructible
+15104,Could not convert value to PropertyValue: %s not supported by PropertyValue
+15105,Could not convert value to IInspectable: %s not supported by IInspectable
+15108,%s: The Inspectable object 'this' is released and cannot be accessed
+15110,'this' is not of expected type: %s
+15112,%s: an unexpected failure occurred while trying to obtain metadata information
+32812,The specified date is not available in the current locale's calendar
diff --git a/src/sentry/data/error-locale/nb-NO.txt b/src/sentry/data/error-locale/nb-NO.txt
new file mode 100644
index 00000000000000..ce08e332d365d6
--- /dev/null
+++ b/src/sentry/data/error-locale/nb-NO.txt
@@ -0,0 +1,289 @@
+5,Ugyldig prosedyrekall eller argument
+6,Overflyt
+7,Ikke nok minne
+9,Indeks utenfor gyldig område
+10,Denne matrisen er fast eller midlertidig låst
+11,Divisjon med null
+13,Ikke samsvar mellom typer
+14,Ikke nok minne for strenger
+17,Kan ikke utføre den forespurte operasjonen
+28,Ikke nok stakkplass
+35,Sub eller Function er ikke definert
+48,Feil ved lasting av DLL
+51,Intern feil
+52,Ugyldig filnavn eller filnummer
+53,Finner ikke filen
+54,Ugyldig filmodus
+55,Filen er allerede åpen
+57,I/U-feil på enhet
+58,Filen finnes allerede
+61,Disken er full
+62,Inndata etter slutten på filen
+67,For mange filer
+68,Enheten er utilgjengelig
+70,Ingen tilgang
+71,Disken er ikke klar
+74,Kan ikke gi nytt navn på tvers av stasjoner
+75,Feil ved tilgang til bane/fil
+76,Finner ikke banen
+91,Objektvariabel eller With-blokkvariabel er ikke angitt
+92,For-løkken er ikke initialisert
+94,Ugyldig bruk av Null
+322,Kan ikke opprette nødvendig midlertidig fil
+424,Krever objekt
+429,Automatiseringsserveren kan ikke opprette objektet
+430,Klassen støtter ikke automatisering
+432,Fant ikke filnavn eller klassenavn under automatiseringsoperasjonen
+438,Objektet støtter ikke angitt egenskap eller metode
+440,Automatiseringsfeil
+445,Objektet støtter ikke denne handlingen
+446,Objektet støtter ikke navngitte argumenter
+447,Objektet støtter ikke gjeldende nasjonale innstilling
+448,Finner ikke navngitt argument
+449,Argumentet er ikke valgfritt
+450,Feil antall argumenter eller ugyldig tilordning av egenskap
+451,Objektet er ikke en samling
+453,Funksjonen er ikke definert i angitt DLL
+458,Variabelen bruker en type automatisering som ikke støttes i JavaScript
+462,Den eksterne serveren finnes ikke eller er ikke tilgjengelig
+501,Kan ikke tilordne til variabel
+502,Objektet kan ikke skriptes
+503,Objektet kan ikke initialiseres
+504,Objektet kan ikke opprettes
+507,Det oppstod et unntak
+1001,Ikke nok minne
+1002,Syntaksfeil
+1003,Forventet ':'
+1004,Forventet ';'
+1005,Forventet '('
+1006,Forventet ')'
+1007,Forventet ']'
+1008,Forventet '{'
+1009,Forventet '}'
+1010,Forventet identifikator
+1011,Forventet '='
+1012,Forventet '/'
+1013,Ugyldig tall
+1014,Ugyldig tegn
+1015,Uavsluttet strengkonstant
+1016,Uavsluttet kommentar
+1018,'return'-setning utenfor funksjon
+1019,Kan ikke ha 'break' utenfor løkke
+1020,Kan ikke ha 'continue' utenfor løkke
+1023,Forventet heksadesimalt siffer
+1024,Forventet 'while'
+1025,Etiketten er definert på nytt
+1026,Finner ikke etiketten
+1027,'default' kan bare brukes én gang i en 'switch'-setning
+1028,Forventet identifikator, streng eller tall
+1029,Forventet '@end'
+1030,Betinget kompilering er deaktivert
+1031,Forventet konstant
+1032,Forventet '@'
+1033,Forventet 'catch'
+1034,Forventet 'var'
+1035,throw må etterfølges av et uttrykk på samme kildelinje
+1037,with-setninger er ikke tillatt i streng modus
+1038,Dupliserte navn på formelle parametere er ikke tillatt i streng modus
+1039,Oktale numeriske litteraler og Escape-tegn er ikke tillatt i streng modus
+1041,Ugyldig bruk av eval i streng modus
+1042,Ugyldig bruk av arguments i streng modus
+1045,Kall av sletting i uttrykk er ikke tillatt i streng modus
+1046,Flere definisjoner av en egenskap er ikke tillatt i streng modus
+1047,I streng modus kan ikke funksjonsdeklarasjoner være nestet inni en setning eller blokk. De kan bare forekomme på øverste nivå eller direkte inni en funksjonstekst.
+1048,Bruken av et nøkkelord for en identifikator er ugyldig
+1049,Bruken av et fremtidig reservert ord for en identifikator er ugyldig
+1050,Bruken av et fremtidig reservert ord for en identifikator er ugyldig. Identifikatornavnet er reservert i streng modus.
+1051,Setter-funksjoner må ha ett argument
+4096,JavaScript-kompileringsfeil
+4097,JavaScript-kjøretidsfeil
+4098,Ukjent kjøretidsfeil
+5000,Kan ikke tilordne til 'this'
+5001,Forventet et tall
+5002,Forventet en funksjon
+5003,Kan ikke tilordne til et funksjonsresultat
+5004,Kan ikke indeksere objektet
+5005,Forventet en streng
+5006,Forventet et datoobjekt
+5007,Forventet et objekt
+5008,Ugyldig venstre side i tilordning
+5009,Udefinert identifikator
+5010,Forventet en boolsk verdi
+5011,Kan ikke utføre kode fra et frigitt skript
+5012,Forventet medlem i et objekt
+5013,Forventet VBArray
+5014,Forventet JavaScript-objekt
+5015,Forventet oppregningsobjekt
+5016,Forventet et vanlig uttrykk
+5017,Syntaksfeil i vanlig uttrykk
+5018,Uventet mengdeoperator
+5019,Forventet ']' i vanlig uttrykk
+5020,Forventet ')' i vanlig uttrykk
+5021,Ugyldig område i tegnsett
+5022,Unntak forkastet og ikke behandlet
+5023,Funksjonen mangler gyldig prototypeobjekt
+5024,URIen som skal krypteres, inneholder et ugyldig tegn
+5025,URIen som skal dekodes, har ikke gyldig kryptering
+5026,Antall desimaler er utenfor tillatt område
+5027,Presisjonen er utenfor tillatt område
+5028,Forventet objekt av typen "Array" eller "arguments"
+5029,Matriselengden skal være en endelig, positiv størrelse
+5030,Matriselengden må ha en tilordnet endelig, positiv størrelse
+5031,Forventet et matriseobjekt
+5034,Sirkelreferanse i verdiargument støttes ikke
+5035,Ugyldig erstatterargument
+5038,Argumentlisten er for stor til å brukes
+5039,Ny deklarering av konstant egenskap
+5041,Objektmedlem kan ikke konfigureres
+5042,Variabel ikke definert i streng modus
+5043,Tilgang til egenskapen caller for en funksjon eller et argumentobjekt er ikke tillatt i streng modus
+5044,Tilgang til egenskapen callee for et argumentobjekt er ikke tillatt i streng modus
+5045,Tilordning til skrivebeskyttede egenskaper er ikke tillatt i streng modus
+5046,Kan ikke opprette egenskap for et ikke-utvidbart objekt
+5047,Forventet et objekt
+5048,Forventet et objekt
+5049,Forventet et objekt
+5050,Forventet et objekt
+5051,Forventet en funksjon
+5052,Forventet en funksjon
+5053,Egenskapen kan ikke ha både aksessorer og en verdi
+5054,"this" er null eller udefinert
+5055,Forventet et objekt
+5056,Forventet en funksjon
+5057,Forventet en streng
+5058,Forventet en boolsk verdi
+5059,Forventet dato
+5060,Forventet et tall
+5061,Forventet VBArray
+5062,Forventet JavaScript-objekt
+5063,Forventet et oppregningsobjekt
+5064,Forventet RegExp-objekt
+5065,Ugyldig funksjonsargument
+5066,Forventet et objekt
+5067,Forventet JavaScript-objekt
+5068,Forventet en funksjon
+5069,Forventet VBArray
+5070,Forventet et objekt
+5071,Forventet et objekt
+5072,Ugyldig length-egenskap
+5073,Forventet et matrise- eller argumentobjekt
+5074,Ugyldig operand
+5075,Ugyldig operand
+5076,Ugyldig egenskapsbeskrivelse
+5077,Kan ikke definere egenskapen fordi objektet ikke kan utvides
+5078,Kan ikke omdefinere egenskapen som ikke kan konfigureres
+5079,Kan ikke endre egenskapen som ikke kan skrives
+5080,Kan ikke endre egenskapen fordi lengden ikke kan skrives
+5081,Kan ikke definere egenskapen
+5082,Ugyldig konstruktørargument for typet matrise
+5083,this er ikke et objekt for typet matrise
+5084,Ugyldig forskyvning/lengde ved oppretting av typet matrise
+5085,Ugyldig start-/sluttverdi i undermatrisemetode for typet matrise
+5086,Ugyldig kilde i typet matrisesett
+5087,this er ikke et DataView-objekt
+5088,Ugyldige argumenter i DataView
+5089,Tilgang til DataView-operasjon utenfor angitt bufferlengde
+5090,Ugyldige argumenter i DataView
+5091,ugyldig funksjonssignatur
+5092,ugyldig egenskapssignatur
+5093,ugyldig inndataparametertype
+5094,ugyldig utdataparameter
+5095,Tilgang til egenskapen arguments for en funksjon er ikke tillatt i streng modus
+5096,Forventet inspiserbart objekt
+5097,Kan ikke konvertere argumentet til typen char
+5098,Kan ikke konvertere argumentet til typen GUID
+5099,Forventet IInspectable
+5100,Kan ikke konvertere objektet til struktur: Objekt mangler forventet egenskap
+5101,Ukjent type
+5102,Funksjonen ble kalt med for få argumenter
+5103,Typen er ikke konstruerbar
+5104,Kan ikke konvertere verdien til PropertyValue: Typen støttes ikke av PropertyValue
+5105,Kan ikke konvertere verdien til IInspectable: Typen støttes ikke av IInspectable
+5106,Kan ikke konvertere dato til Windows.Foundation.DateTime: Verdien er utenfor gyldig område
+5107,Kan ikke konvertere verdi til Windows.Foundation.TimeSpan: Verdien er utenfor gyldig område
+5108,Ugyldig tilgang til et allerede frigitt inspiserbart objekt
+5109,Kan ikke frigi et allerede frigitt inspiserbart objekt
+5110,this er ikke av den forventede typen
+5111,Ugyldig lengde og størrelse angitt for matrisen
+5112,Det oppstod en utventet feil under forsøk på å hente metadatainformasjon
+5200,Statusen er "feil", men getResults returnerte ingen feil
+5201,Manglende eller ugyldig statusparameter sendt til fullført behandlingsprogram
+5202,Manglende eller ugyldig avsenderparameter sendt til fullført behandlingsprogram
+6000,Uendelighet
+6001,-Uendelighet
+10438,Objektet støtter ikke egenskapen eller metoden %s
+10449,Argument for funksjonen %s er ikke valgfritt
+15001,%s er ikke et tall
+15002,%s er ikke en funksjon
+15004,%s er ikke et indekserbart objekt
+15005,%s er ikke en streng
+15006,%s er ikke et datoobjekt
+15007,%s er null eller ikke et objekt
+15008,Kan ikke tilordne til %s
+15009,%s er udefinert
+15010,%s er ikke en boolsk verdi
+15012,Kan ikke slette %s
+15013,%s er ikke VBArray
+15014,%s er ikke et JavaScript-objekt
+15015,%s er ikke et oppregningsobjekt
+15016,%s er ikke et vanlig uttrykk
+15028,%s er ikke et objekt av typen Array eller arguments
+15031,%s er ikke et Array-objekt
+15036,%s-attributtet i egenskapsbeskrivelsen kan ikke settes til true for dette objektet
+15037,%s-attributtet i egenskapsbeskrivelsen kan ikke settes til false for dette objektet
+15039,Ny deklarering av konstanten %s
+15041,Kall av sletting i %s er ikke tillatt i streng modus
+15047,Kan ikke angi egenskapen %s når den er udefinert eller har nullreferanse
+15048,Kan ikke hente egenskapen %s når den er udefinert eller har nullreferanse
+15049,Kan ikke slette egenskapen %s når den er udefinert eller har nullreferanse
+15050,Ingen tilgang til egenskapen %s: VarDate-typen støtter ikke brukerdefinerte egenskaper
+15051,Verdien for egenskapen %s er ikke et funksjonsobjekt
+15052,Verdien for egenskapen %s er null eller udefinert, ikke et funksjonsobjekt
+15054,%s: "this" er null eller udefinert
+15055,%s: "this" er ikke et objekt
+15056,%s: "this" er ikke et funksjonsobjekt
+15057,%s: "this" er ikke et strengeobjekt
+15058,%s: "this" er ikke et boolsk objekt
+15059,%s: "this" er ikke et datoobjekt
+15060,%s: "this" er ikke et nummerobjekt
+15061,%s: "this" er ikke et VBArray-objekt
+15062,%s: "this" er ikke et JavaScript-objekt
+15063,%s: "this" er ikke et oppregningsobjekt
+15064,%s: "this" er ikke et RegExp-objekt
+15065,%s: ugyldig argument
+15066,%s: argumentet er ikke et objekt
+15067,%s: argumentet er ikke et JavaScript-objekt
+15068,%s: argumentet er ikke et funksjonsobjekt
+15069,%s: argumentet er ikke et VBArray-objekt
+15070,%s: argumentet er null eller udefinert
+15071,%s: argumentet er ikke et objekt og er ikke null
+15072,%s: argumentet har ingen gyldig length-egenskap
+15073,%s: Forventet et matrise- eller argumentobjekt
+15074,Ugyldig operand for %s: forventet objekt
+15075,Ugyldig operand for %s: forventet funksjon
+15076,Ugyldig beskrivelse for egenskapen %s
+15077,Kan ikke definere egenskapen %s fordi objektet ikke kan utvides
+15078,Kan ikke omdefinere egenskapen %s som ikke kan konfigureres
+15079,Kan ikke endre egenskapen %s som ikke kan skrives
+15080,Kan ikke endre egenskapen %s fordi lengden ikke kan skrives
+15081,Kan ikke definere egenskapen %s
+15088,Nødvendig argument %s i DataView-metode er ikke angitt
+15090,DataView-konstruktørargumentet %s er ugyldig
+15091,Funksjonen %s har en ugyldig signatur, og kan ikke kalles
+15092,Egenskapen %s har en ugyldig signatur, og er ikke tilgjengelig
+15093,Kjøretidsklassen %s, som har Windows.Foundation.IPropertyValue som standardgrensesnitt, støttes ikke som inndataparametertype
+15094,Objektet med grensesnittet Windows.Foundation.IPropertyValue som har kjøretidsklassenavnet %s, støttes ikke som utdataparameter
+15096,%s: "this" er ikke et inspiserbart objekt
+15097,%s: Kan ikke konvertere argumentet til typen char
+15098,%s: Kan ikke konvertere argumentet til typen GUID
+15099,%s: Kan ikke konvertere returverdi til IInspectable
+15100,Kan ikke konvertere objekt til struktur: Objektet mangler den forventede egenskapen %s
+15101,Finner ikke typen %s
+15102,%s: Funksjonen ble kalt med for få argumenter
+15103,%s: Typen er ikke konstruerbar
+15104,Kan ikke konvertere verdien til PropertyValue: %s støttes ikke av PropertyValue
+15105,Kan ikke konvertere verdien til IInspectable: %s støttes ikke av IInspectable
+15108,%s: Det inspiserbare objektet "this" er frigitt og kan ikke brukes
+15110,this er ikke av den forventede typen: %s
+15112,%s: det oppstod en uventet feil under forsøk på å hente metadatainformasjon
+32812,Den angitte datoen er ikke tilgjengelig i kalenderen under de gjeldende nasjonale innstillingene
diff --git a/src/sentry/data/error-locale/nl-NL.txt b/src/sentry/data/error-locale/nl-NL.txt
new file mode 100644
index 00000000000000..1ce5b5ee8d43ce
--- /dev/null
+++ b/src/sentry/data/error-locale/nl-NL.txt
@@ -0,0 +1,289 @@
+5,Ongeldige procedureaanroep of ongeldig argument
+6,Overloop
+7,Onvoldoende geheugen
+9,Het subscript valt buiten het bereik
+10,Deze matrix ligt vast of is tijdelijk vergrendeld.
+11,Deling door nul
+13,Typen komen niet met elkaar overeen
+14,Onvoldoende tekenreeksruimte
+17,Kan de opgegeven bewerking niet uitvoeren
+28,Onvoldoende stackruimte
+35,Sub of Function is niet gedefinieerd
+48,Fout bij laden van DLL
+51,Interne fout
+52,Bestandsnaam of -nummer onjuist
+53,Bestand is niet gevonden
+54,Ongeldige bestandsmodus
+55,Het bestand is al geopend
+57,I/O-fout in apparaat
+58,Het bestand bestaat al
+61,De schijf is vol
+62,Invoer voorbij bestandseinde
+67,Er zijn te veel bestanden geopend
+68,Apparaat niet beschikbaar
+70,Toegang geweigerd
+71,De schijf is niet gereed
+74,Naam wijzigen met ander station niet toegestaan
+75,Toegangsfout bij pad of bestand
+76,Kan het pad niet vinden
+91,Objectvariabele of blokvariabele With is niet ingesteld
+92,De For-lus is niet geïnitialiseerd
+94,Ongeldig gebruik van Null
+322,Kan het benodigde tijdelijke bestand niet maken
+424,Object vereist
+429,Automatiseringsonderdeel kan object niet maken
+430,Automatisering wordt niet ondersteund door deze klasse
+432,De bestandsnaam of klassennaam is niet gevonden tijdens de Automation-bewerking
+438,Deze eigenschap of methode wordt niet ondersteund door dit object
+440,Automatiseringsfout
+445,Deze actie wordt niet ondersteund door dit object
+446,Benoemde argumenten worden niet ondersteund door het object
+447,De huidige landinstellingen worden niet ondersteund door het object
+448,Kan benoemd argument niet vinden
+449,Het argument is niet optioneel
+450,Onjuist aantal argumenten of ongeldige eigenschaptoewijzingen
+451,Dit object is geen collectie
+453,Kan opgegeven DLL-functie niet vinden
+458,Variabele maakt gebruik van een automatiseringstype dat niet door JavaScript wordt ondersteund
+462,De externe servercomputer bestaat niet of is niet beschikbaar
+501,Kan niets toewijzen aan een variabele
+502,Object kan niet veilig in script worden gebruikt
+503,Object kan niet op een veilige manier worden geïnitialiseerd
+504,Object kan niet op een veilige manier worden gemaakt
+507,Er is een uitzondering opgetreden
+1001,Onvoldoende geheugen
+1002,Syntaxisfout
+1003,':' wordt verwacht
+1004,';' wordt verwacht
+1005,'(' wordt verwacht
+1006,')' wordt verwacht
+1007,']' wordt verwacht
+1008,'{' wordt verwacht
+1009,'}' wordt verwacht
+1010,Id wordt verwacht
+1011,'=' wordt verwacht
+1012,'/' wordt verwacht
+1013,Ongeldig getal
+1014,Ongeldig teken
+1015,Tekenreeksconstante is niet afgesloten
+1016,Opmerking is niet afgesloten
+1018,'return' instructie bevindt zich buiten de functie
+1019,'break' buiten de lus is niet mogelijk
+1020,'continue' buiten de lus is niet mogelijk
+1023,Hexadecimaal teken wordt verwacht
+1024,'while' wordt verwacht
+1025,Naam is opnieuw gedefinieerd
+1026,Kan naam niet vinden
+1027,'default' mag niet meer dan een keer voorkomen in een 'switch' instructie
+1028,Id, tekenreeks of getal wordt verwacht
+1029,'@end' wordt verwacht
+1030,Voorwaardelijke compilatie is uitgeschakeld
+1031,Constante wordt verwacht
+1032,'@' wordt verwacht
+1033,'catch' wordt verwacht
+1034,'var' wordt verwacht
+1035,'throw' moet worden gevolgd door een expressie op dezelfde bronregel
+1037,'with'-instructies zijn niet toegestaan in de strikte modus
+1038,Dubbele formele parameternamen zijn niet toegestaan in de strikte modus
+1039,Octale numerieke waardes en escapetekens zijn niet toegestaan in de strikte modus
+1041,Ongeldig gebruik van 'eval' in de strikte modus
+1042,Ongeldig gebruik van 'arguments' in de strikte modus
+1045,Het verwijderen van een expressie is niet toegestaan in de strikte modus
+1046,Meerdere definities van een eigenschap zijn niet toegestaan in de strikte modus
+1047,In de strikte modus kunnen functiedeclaraties niet worden genest in een instructie of blok. Ze kunnen alleen op het hoogste niveau of rechtstreeks in de hoofdtekst van een functie voorkomen.
+1048,Het gebruik van een trefwoord voor een id is ongeldig
+1049,Het gebruik van een toekomstig gereserveerd woord voor een id is ongeldig
+1050,Het gebruik van een toekomstig gereserveerd woord voor een id is ongeldig. De id-naam is gereserveerd in de strikte modus.
+1051,Functies voor insteller moeten één argument hebben
+4096,Compilatiefout JavaScript
+4097,Runtime-fout JavaScript
+4098,Onbekende runtime-fout
+5000,Kan niet worden toegewezen aan 'this'
+5001,Getal wordt verwacht
+5002,Functie wordt verwacht
+5003,Kan niet worden toegewezen aan het resultaat van een functie
+5004,Kan object niet indexeren
+5005,Tekenreeks wordt verwacht
+5006,Datumobject wordt verwacht
+5007,Object wordt verwacht
+5008,Ongeldige linkerkant van toewijzing
+5009,Niet-gedefinieerde aanduiding
+5010,Boole-waarde wordt verwacht
+5011,Kan programmacode niet uitvoeren vanuit een vrijgegeven script
+5012,Objectlid wordt verwacht
+5013,VBArray wordt verwacht
+5014,JavaScript-object wordt verwacht
+5015,Enumeratorobject wordt verwacht
+5016,Regulier expressieobject wordt verwacht
+5017,Syntaxisfout in reguliere expressie
+5018,Onverwachte bepaler
+5019,']' wordt verwacht in reguliere expressie
+5020,')' wordt verwacht in reguliere expressie
+5021,Ongeldig bereik in tekenset
+5022,Uitzondering geactiveerd maar niet afgehandeld
+5023,Functie heeft geen geldig prototype-object
+5024,De te coderen URI bevat een ongeldig teken
+5025,De te decoderen URI is geen geldige codering
+5026,Het aantal cijfers in de breuk valt buiten het bereik
+5027,De precisie valt buiten het bereik
+5028,Array- of Arguments-object wordt verwacht
+5029,Lengte van Array-object moet een positieve integer zijn
+5030,Lengte van Array-object moet een eindig positief getal zijn
+5031,Array-object wordt verwacht
+5034,Kringverwijzing in waardeargument wordt niet ondersteund
+5035,Ongeldig vervangingsargument
+5038,De argumentenlijst is te groot om te worden toegepast
+5039,Herdeclaratie van constante eigenschap
+5041,Objectlid kan niet worden geconfigureerd
+5042,Variabele is niet gedefinieerd in de strikte modus
+5043,Het openen van de eigenschap 'caller' van een functie- of Arguments-object is niet toegestaan in de strikte modus
+5044,Het openen van de eigenschap 'callee' van een Arguments-object is niet toegestaan in de strikte modus
+5045,Toewijzing aan alleen-lezen eigenschappen is niet toegestaan in de strikte modus
+5046,Kan geen eigenschap maken voor een niet-uitbreidbaar object
+5047,Object wordt verwacht
+5048,Object wordt verwacht
+5049,Object wordt verwacht
+5050,Object wordt verwacht
+5051,Functie wordt verwacht
+5052,Functie wordt verwacht
+5053,De eigenschap kan niet zowel accessors als een waarde hebben
+5054,'this' is null of niet gedefinieerd
+5055,Object wordt verwacht
+5056,Functie wordt verwacht
+5057,Tekenreeks wordt verwacht
+5058,Boole-waarde wordt verwacht
+5059,Datum wordt verwacht
+5060,Getal wordt verwacht
+5061,VBArray wordt verwacht
+5062,JavaScript-object wordt verwacht
+5063,Enumeratorobject wordt verwacht
+5064,RegExp-object wordt verwacht
+5065,Ongeldig functieargument
+5066,Object wordt verwacht
+5067,JavaScript-object wordt verwacht
+5068,Functie wordt verwacht
+5069,VBArray wordt verwacht
+5070,Object wordt verwacht
+5071,Object wordt verwacht
+5072,Ongeldige lengte-eigenschap
+5073,Array- of Arguments-object wordt verwacht
+5074,Ongeldige operand
+5075,Ongeldige operand
+5076,Ongeldige eigenschapsdescriptor
+5077,Kan de eigenschap niet definiëren: object is niet uitbreidbaar
+5078,Kan de niet-configureerbare eigenschap niet opnieuw definiëren
+5079,Kan de niet-schrijfbare eigenschap niet wijzigen
+5080,Kan de eigenschap niet wijzigen: 'length' is niet schrijfbaar
+5081,Kan de eigenschap niet definiëren
+5082,Constructorargument van getypeerde matrix is ongeldig
+5083,'this' is geen getypeerd matrixobject
+5084,Ongeldige offset/lengte bij het maken van een getypeerde matrix
+5085,Ongeldige begin-/eindwaarde in submatrix-methode van getypeerde matrix
+5086,Ongeldige bron in getypeerde matrixset
+5087,'this' is geen DataView-object
+5088,Ongeldige argumenten in DataView
+5089,Toegang tot DataView-bewerking buiten opgegeven bufferlengte
+5090,Ongeldige argumenten in DataView
+5091,ongeldige functiehandtekening
+5092,ongeldige eigenschapshandtekening
+5093,ongeldig type invoerparameter
+5094,Ongeldige invoerparameter
+5095,Toegang tot de eigenschap 'arguments' van een functie is niet toegestaan in de strikte modus
+5096,Inspecteerbaar object verwacht
+5097,Kan argument niet converteren naar type 'char'
+5098,Kan argument niet converteren naar type 'GUID'
+5099,IInspectable verwacht
+5100,Kan object niet converteren naar structuur: verwachte eigenschap ontbreekt in object
+5101,Onbekend type
+5102,Functie is aangeroepen met te weinig argumenten
+5103,Kan type niet maken
+5104,Kan waarde niet converteren naar PropertyValue: type wordt niet ondersteund door PropertyValue
+5105,Kan waarde niet converteren naar IInspectable: type wordt niet ondersteund door IInspectable
+5106,Kan datum niet converteren naar Windows.Foundation.DateTime: waarde ligt buiten het geldige bereik
+5107,Kan waarde niet converteren naar Windows.Foundation.TimeSpan: waarde ligt buiten het geldige bereik
+5108,Ongeldige toegang tot reeds vrijgegeven inspecteerbaar object
+5109,Kan reeds vrijgegeven inspecteerbaar object niet vrijgeven
+5110,'dit' is niet van het verwachte type
+5111,Ongeldige lengte en grootte opgegeven voor de matrix
+5112,Er is een onverwachte fout opgetreden bij het opvragen van metagegevens
+5200,Status is 'fout', maar getResults heeft geen fout geretourneerd
+5201,Ontbrekende of ongeldige statusparameter doorgegeven aan voltooide handler
+5202,Ontbrekende of ongeldige afzenderparameter doorgegeven aan voltooide handler
+6000,Oneindig
+6001,-Oneindig
+10438,De eigenschap of methode %s wordt niet ondersteund door dit object
+10449,Het argument voor de functie %s is niet optioneel
+15001,%s is geen getal
+15002,%s is geen functie
+15004,%s kan als object niet worden geïndexeerd
+15005,%s is geen tekenreeks
+15006,%s is geen datumobject
+15007,%s is leeg of geen object
+15008,Kan niets toewijzen aan %s
+15009,%s is niet gedefinieerd
+15010,%s is geen boole-waarde
+15012,Kan %s niet verwijderen
+15013,%s is geen VBArray
+15014,%s is geen JavaScript-object
+15015,%s is geen enumeratorobject
+15016,%s is geen regulier expressieobject
+15028,%s is geen Array- of Arguments-object
+15031,%s is geen Array-object
+15036,Het kenmerk %s in de eigenschapsdescriptor kan voor dit object niet worden ingesteld op true.
+15037,Het kenmerk %s in de eigenschapsdescriptor kan voor dit object niet worden ingesteld op false.
+15039,Herdeclaratie van de constante %s
+15041,Het verwijderen van %s is niet toegestaan in de strikte modus
+15047,Kan de eigenschap %s van een niet-gedefinieerde verwijzing of een verwijzing naar een lege waarde niet instellen
+15048,Kan de eigenschap %s van een niet-gedefinieerde verwijzing of een verwijzing naar een lege waarde niet ophalen
+15049,Kan de eigenschap %s van een niet-gedefinieerde verwijzing of een verwijzing naar een lege waarde niet verwijderen
+15050,Kan de eigenschap %s niet openen: het type VarDate biedt geen ondersteuning voor door gebruikers gedefinieerde eigenschappen
+15051,De waarde van de eigenschap %s is geen functieobject
+15052,De waarde van de eigenschap %s null of niet gedefinieerd en geen functieobject
+15054,%s: 'this' is null of niet gedefinieerd
+15055,%s: 'this' is geen object
+15056,%s: 'this' is geen functieobject
+15057,%s: 'this' is geen tekenreeksobject
+15058,%s: 'this' is geen boole-object
+15059,%s: 'this' is geen datumobject
+15060,%s: 'this' is geen getalobject
+15061,%s: 'this' is geen VBArray-object
+15062,%s: 'this' is geen JavaScript-object
+15063,%s: 'this' is geen enumeratorobject
+15064,%s: 'this' is geen RegExp-object
+15065,%s: ongeldig argument
+15066,%s: argument is geen object
+15067,%s: argument is geen JavaScript-object
+15068,%s: argument is geen functieobject
+15069,%s: argument is geen VBArray-object
+15070,%s: argument is null of niet gedefinieerd
+15071,%s: argument is geen object en is niet null
+15072,%s: argument heeft geen geldige lengte-eigenschap
+15073,%s: Array- of Arguments-object wordt verwacht
+15074,Ongeldige operand naar %s: object wordt verwacht
+15075,Ongeldige operand naar %s: functie wordt verwacht
+15076,Ongeldige descriptor voor eigenschap %s
+15077,Kan de eigenschap %s niet definiëren: object is niet uitbreidbaar
+15078,Kan de niet-configureerbare eigenschap %s niet opnieuw definiëren
+15079,Kan de niet-schrijfbare eigenschap %s niet wijzigen
+15080,Kan de eigenschap %s niet wijzigen: 'length' is niet schrijfbaar
+15081,Kan de eigenschap %s niet definiëren
+15088,Vereist argument %s in de DataView-methode is niet opgegeven
+15090,DataView-constructorargument %s is ongeldig
+15091,De functie '%s' heeft een ongeldige handtekening en kan niet worden aangeroepen
+15092,De eigenschap '%s' heeft een ongeldige handtekening en kan niet worden geopend
+15093,De runtime-klasse %s met Windows.Foundation.IPropertyValue als standaardinterface wordt niet ondersteund als type invoerparameter
+15094,Het object met interface Windows.Foundation.IPropertyValue die de runtime-klassenaam %s heeft, wordt niet ondersteund als uitvoerparameter
+15096,%s: 'this' is geen inspecteerbaar object
+15097,%s: kan argument niet converteren naar type 'char'
+15098,%s: kan argument niet converteren naar type 'GUID'
+15099,%s: kan retourwaarde niet converteren naar IInspectable
+15100,Kan object niet converteren naar structuur: verwachte eigenschap '%s' ontbreekt in object
+15101,Type '%s' niet gevonden
+15102,%s: functie is aangeroepen met te weinig argumenten
+15103,%s: kan type niet maken
+15104,Kan waarde niet converteren naar PropertyValue: %s wordt niet ondersteund door PropertyValue
+15105,Kan waarde niet converteren naar IInspectable: %s wordt niet ondersteund door IInspectable
+15108,%s: het inspecteerbare object 'this' is vrijgegeven en kan niet worden geopend
+15110,'dit' is niet van het verwachte type: %s
+15112,%s: er is een onverwachte fout opgetreden bij het opvragen van metagegevens
+32812,De opgegeven datum is niet beschikbaar in de agenda van de huidige landinstellingen
diff --git a/src/sentry/data/error-locale/pl-PL.txt b/src/sentry/data/error-locale/pl-PL.txt
new file mode 100644
index 00000000000000..7e28f4a11a0a35
--- /dev/null
+++ b/src/sentry/data/error-locale/pl-PL.txt
@@ -0,0 +1,289 @@
+5,Nieprawidłowe wywołanie procedury lub nieprawidłowy argument
+6,Przepełnienie
+7,Za mało pamięci
+9,Indeks poza zakresem
+10,Ta tablica ma stały rozmiar lub jest chwilowo zablokowana.
+11,Dzielenie przez zero
+13,Niezgodność typów
+14,Za mało miejsca na ciąg znaków
+17,Nie można wykonać żądanej operacji.
+28,Za mało miejsca na stosie
+35,Niezdefiniowany podprogram lub funkcja
+48,Błąd podczas ładowania biblioteki DLL
+51,Błąd wewnętrzny
+52,Zła nazwa lub numer pliku
+53,Nie można odnaleźć pliku.
+54,Zły tryb pliku
+55,Plik jest już otwarty.
+57,Błąd We/Wy urządzenia
+58,Plik już istnieje.
+61,Dysk zapełniony
+62,Próba zapisu poza końcem pliku
+67,Za dużo plików
+68,Urządzenie niedostępne
+70,Brak uprawnień
+71,Dysk nie jest gotowy.
+74,Nie można zmienić nazwy używając innego dysku.
+75,Błąd dostępu do ścieżki/pliku
+76,Nie można odnaleźć ścieżki.
+91,Zmienna obiektowa lub zmienna bloku With nie jest ustawiona.
+92,Nie zainicjowana pętla For
+94,Nieprawidłowe użycie Null
+322,Nie można utworzyć niezbędnego pliku tymczasowego.
+424,Wymagany jest obiekt
+429,Serwer automatyzacji nie może utworzyć obiektu.
+430,Klasa nie obsługuje automatyzacji.
+432,Podczas operacji automatyzacji nie można odnaleźć nazwy pliku lub klasy.
+438,Obiekt nie obsługuje tej właściwości lub metody.
+440,Błąd automatyzacji
+445,Obiekt nie obsługuje tej operacji.
+446,Obiekt nie obsługuje argumentów nazwanych.
+447,Obiekt nie obsługuje bieżących ustawień regionalnych.
+448,Nie można odnaleźć nazwanego argumentu.
+449,Argument nie jest opcjonalny.
+450,Zła liczba argumentów lub nieprawidłowe przyporządkowanie właściwości
+451,Obiekt nie jest kolekcją.
+453,Nie można odnaleźć podanej funkcji DLL.
+458,Zmienna używa typu automatyzacji, który nie jest obsługiwany przez język JavaScript
+462,Serwer zdalny nie istnieje lub jest niedostępny
+501,Nie można wykonać przypisania do zmiennej
+502,Obiektu nie należy używać w skryptach.
+503,Obiektu nie należy inicjować.
+504,Nie można bezpiecznie utworzyć obiektu.
+507,Wystąpił wyjątek
+1001,Za mało pamięci
+1002,Błąd składni
+1003,Oczekiwano znaku ':'
+1004,Oczekiwano znaku ';'
+1005,Oczekiwano znaku '('
+1006,Oczekiwano znaku ')'
+1007,Oczekiwano znaku ']'
+1008,Oczekiwano znaku '{'
+1009,Oczekiwano znaku '}'
+1010,Oczekiwano identyfikatora
+1011,Oczekiwano znaku '='
+1012,Oczekiwano znaku '/'
+1013,Nieprawidłowa liczba
+1014,Nieprawidłowy znak
+1015,Brak zakończenia stałej znakowej
+1016,Brak zakończenia komentarza
+1018,Instrukcja 'return' jest poza funkcją.
+1019,Instrukcji 'break' nie można używać poza pętlą.
+1020,Instrukcji 'continue' nie można używać poza pętlą.
+1023,Oczekiwano liczby heksadecymalnej
+1024,Oczekiwano słowa kluczowego 'while'
+1025,Ponowna definicja etykiety
+1026,Nie można odnaleźć etykiety.
+1027,Słowo kluczowe 'default' w zakresie instrukcji 'switch' może wystąpić tylko raz
+1028,Oczekiwano identyfikatora, ciągu znaków lub liczby
+1029,Oczekiwano '@end'
+1030,Kompilacja warunkowa jest wyłączona
+1031,Oczekiwano stałej
+1032,Oczekiwano znaku '@'
+1033,Oczekiwano 'catch'
+1034,Oczekiwano 'var'
+1035,po instrukcji „throw” powinno występować wyrażenie w tym samym wierszu kodu
+1037,instrukcje „with” są niedozwolone w trybie standardów
+1038,Zduplikowane nazwy parametrów formalnych nie są dozwolone w trybie standardów
+1039,Stałe liczbowe zapisane ósemkowo i znaki ucieczki są niedozwolone w trybie standardów
+1041,Nieprawidłowe użycie wyrażenia „eval” w trybie standardów
+1042,Nieprawidłowe użycie wyrażenia „arguments” w trybie standardów
+1045,Wywołanie usunięcia w odniesieniu do wyrażenie jest niedozwolone w trybie standardów
+1046,Wiele definicji właściwości jest niedozwolone w trybie standardów
+1047,W trybie standardów deklaracje funkcji nie mogą być zagnieżdżone wewnątrz instrukcji lub bloku. Mogą występować tylko na najwyższym poziomie lub bezpośrednio wewnątrz treści funkcji.
+1048,Użycie identyfikatora w postaci słowa kluczowego jest nieprawidłowe
+1049,Użycie identyfikatora w postaci słowa zastrzeżonego dla przyszłego użycia jest nieprawidłowe
+1050,Użycie identyfikatora w postaci słowa zastrzeżonego dla przyszłego użycia jest nieprawidłowe. Ta nazwa identyfikatora jest zastrzeżona w trybie standardów.
+1051,Funkcje setter muszą mieć jeden argument
+4096,Błąd kompilacji kodu JavaScript
+4097,Błąd czasu wykonywania kodu JavaScript
+4098,Nieznany błąd czasu wykonywania.
+5000,Nie można wykonać przypisania do 'this'
+5001,Oczekiwano liczby.
+5002,Oczekiwano funkcji.
+5003,Nie można wykonać przypisania do wyniku funkcji.
+5004,Nie można indeksować obiektu.
+5005,Oczekiwano ciągu znaków.
+5006,Oczekiwano obiektu typu Date
+5007,Oczekiwano obiektu.
+5008,Nieprawidłowa lewa strona przypisania
+5009,Niezdefiniowany identyfikator.
+5010,Oczekiwano wartości typu Boolean.
+5011,Nie można wykonać kodu programu ze skryptu uwolnionego.
+5012,Oczekiwano składowej obiektu.
+5013,Oczekiwano obiektu typu VBArray
+5014,Oczekiwano obiektu JavaScript
+5015,Oczekiwano obiektu wyliczeniowego.
+5016,Oczekiwano obiektu wyrażenia zwykłego.
+5017,Błąd składniowy w wyrażeniu zwykłym.
+5018,Nieoczekiwany kwantyfikator.
+5019,Oczekiwano znaku ']' w wyrażeniu zwykłym.
+5020,Oczekiwano znaku ')' w wyrażeniu zwykłym.
+5021,Nieprawidłowy zakres dla zbioru znaków.
+5022,Wyjątek przerzucony i nie wyłapany
+5023,Funkcja nie posiada właściwego obiektu prototypowego
+5024,Adres URL do dekodowania zawiera nieprawidłowy znak
+5025,Adres URL do dekodowania jest nieprawidłowo zakodowany
+5026,Liczba miejsc po przecinku jest poza zakresem
+5027,Dokładność jest poza zakresem
+5028,Oczekiwano obiektów tablicy lub argumentów
+5029,Długość tablicy musi być skończoną, dodatnią liczbą całkowitą
+5030,Długości tablicy musi być przypisana skończona liczba dodatnia
+5031,Oczekiwano obiektu tablicy
+5034,Odwołanie cykliczne w argumencie wartości nie jest obsługiwane
+5035,Nieprawidłowy argument zamiany
+5038,Lista argumentów za duża do zastosowania
+5039,Ponowna deklaracja właściwości const
+5041,Nie można skonfigurować obiektu członkowskiego
+5042,Zmienna niezdefiniowana w trybie standardów
+5043,Uzyskiwanie dostępu do właściwości „caller” obiektu funkcji lub argumentów jest niedozwolone w trybie standardów
+5044,Uzyskiwanie dostępu do właściwości „callee” obiektu argumentów jest niedozwolone w trybie standardów
+5045,Przypisanie właściwości tylko do odczytu jest niedozwolone w trybie standardów
+5046,Nie można utworzyć właściwości dla obiektu nierozszerzalnego
+5047,Oczekiwano obiektu.
+5048,Oczekiwano obiektu.
+5049,Oczekiwano obiektu.
+5050,Oczekiwano obiektu.
+5051,Oczekiwano funkcji.
+5052,Oczekiwano funkcji.
+5053,Właściwość nie może mieć jednocześnie metod dostępu i wartości
+5054,Obiekt „this” jest pusty lub niezdefiniowany
+5055,Oczekiwano obiektu.
+5056,Oczekiwano funkcji.
+5057,Oczekiwano ciągu znaków.
+5058,Oczekiwano wartości typu Boolean.
+5059,Oczekiwano daty
+5060,Oczekiwano liczby.
+5061,Oczekiwano obiektu typu VBArray
+5062,Oczekiwano obiektu JavaScript
+5063,Oczekiwano obiektu wyliczeniowego.
+5064,Oczekiwano obiektu RegExp
+5065,Nieprawidłowy argument funkcji
+5066,Oczekiwano obiektu.
+5067,Oczekiwano obiektu JavaScript
+5068,Oczekiwano funkcji.
+5069,Oczekiwano obiektu typu VBArray
+5070,Oczekiwano obiektu.
+5071,Oczekiwano obiektu.
+5072,Nieprawidłowa właściwość „length”
+5073,Oczekiwano obiektów tablicy lub argumentów
+5074,Nieprawidłowy argument operacji
+5075,Nieprawidłowy argument operacji
+5076,Nieprawidłowy deskryptor właściwości
+5077,Nie można zdefiniować właściwości: obiekt nie jest rozszerzalny
+5078,Nie można ponownie zdefiniować niekonfigurowalnej właściwości
+5079,Nie można zdefiniować niezapisywalnej właściwości
+5080,Nie można zmodyfikować właściwości: wartość „length” jest niezapisywalna
+5081,Nie można zdefiniować właściwości
+5082,Argument konstruktora tablicy z określonym typem jest nieprawidłowy
+5083,„this” nie jest obiektem tablicy z określonym typem
+5084,Nieprawidłowa wartość przesunięcia/długości podczas tworzenia tablicy z określonym typem
+5085,Nieprawidłowa wartość początkowa/końcowa w metodzie tablicy podrzędnej dla tablicy z określonym typem
+5086,Nieprawidłowe źródło w operacji ustawiania tablicy z określonym typem
+5087,„this” nie jest obiektem DataView
+5088,Nieprawidłowe argumenty w obiekcie DataView
+5089,Dostęp do lokalizacji spoza określonej długości bufora w operacji obiektu DataView
+5090,Nieprawidłowe argumenty w obiekcie DataView
+5091,nieprawidłowa sygnatura funkcji
+5092,nieprawidłowa sygnatura właściwości
+5093,nieprawidłowy typ parametru wejściowego
+5094,nieprawidłowy parametr wyjściowy
+5095,Dostęp do właściwości „arguments” funkcji jest niedozwolony w trybie standardów
+5096,Oczekiwano obiektu Inspectable
+5097,Nie można przekonwertować argumentu na typ „char”
+5098,Nie można przekonwertować argumentu na typ „GUID”
+5099,Oczekiwano obiektu IInspectable
+5100,Nie można przekonwertować obiektu na strukturę: w obiekcie brakuje oczekiwanej właściwości
+5101,Nieznany typ
+5102,Wywołano funkcję ze zbyt małą liczbą argumentów
+5103,Typ nie umożliwia konstrukcji
+5104,Nie można przekonwertować wartości na obiekt PropertyValue: typ nie jest obsługiwany przez obiekt PropertyValue
+5105,Nie można przekonwertować wartości na obiekt IInspectable: typ nie jest obsługiwany przez obiekt IInspectable
+5106,Nie można przekonwertować daty na obiekt Windows.Foundation.DateTime: wartość spoza prawidłowego zakresu
+5107,Nie można przekonwertować wartości na obiekt Windows.Foundation.TimeSpan: wartość spoza prawidłowego zakresu
+5108,Nieprawidłowy dostęp do obiektu Inspectable, który został już zwolniony
+5109,Nie można zwolnić obiektu Inspectable, który został już zwolniony
+5110,Typ obiektu „this” jest inny niż oczekiwany.
+5111,Dla tablicy określono niedozwolone długość i rozmiar
+5112,Wystąpił nieoczekiwany błąd podczas próby uzyskania informacji o metadanych
+5200,Stan ma wartość „error” (błąd), ale funkcja getResults nie zwróciła błędu.
+5201,Nieprawidłowy parametr stanu przekazany do dojścia ukończenia lub brak takiego parametru.
+5202,Nieprawidłowy parametr nadawcy przekazany do dojścia ukończenia lub brak takiego parametru.
+6000,Nieskończoność
+6001,-Nieskończoność
+10438,Obiekt nie obsługuje właściwości lub metody „%s”
+10449,Argument dla funkcji „%s” nie jest opcjonalny
+15001,„%s” nie jest liczbą
+15002,„%s” nie jest funkcją
+15004,Obiektu „%s” nie można indeksować
+15005,„%s” nie jest ciągiem znaków
+15006,„%s” nie jest obiektem typu date
+15007,„%s” jest pusty lub nie jest obiektem
+15008,Nie można wykonać przypisania do „%s”
+15009,Brak definicji „%s”
+15010,„%s” nie jest wartością typu Boolean
+15012,Nie można usunąć „%s”
+15013,„%s” nie jest obiektem typu VBArray
+15014,„%s” nie jest obiektem typu JavaScript
+15015,„%s” nie jest obiektem wyliczeniowym
+15016,Symbol „%s” nie jest obiektem wyrażenia regularnego
+15028,%s nie jest obiektem Array ani obiektem argumentów
+15031,%s nie jest obiektem Array
+15036,Atrybut „%s” dotyczący deskryptora właściwości nie może mieć wartości „true” dla tego obiektu
+15037,Atrybut „%s” dotyczący deskryptora właściwości nie może mieć wartości „false” dla tego obiektu
+15039,Ponowna deklaracja stałej „%s”
+15041,Wywołanie usunięcia w odniesieniu do „%s” jest niedozwolone w trybie standardów
+15047,Nie można ustawić właściwości „%s” dla niezdefiniowanego lub pustego odwołania
+15048,Nie można pobrać właściwości „%s” dla niezdefiniowanego lub pustego odwołania
+15049,Nie można usunąć właściwości „%s” dla niezdefiniowanego lub pustego odwołania
+15050,Nie można uzyskać dostępu do właściwości „%s”: typ „VarDate” nie obsługuje właściwości zdefiniowanych przez użytkownika
+15051,Wartość właściwości „%s” nie jest obiektem Function
+15052,Wartość właściwości „%s” jest pusta lub niezdefiniowana, nie jest obiektem Function
+15054,%s: obiekt „this” jest pusty lub niezdefiniowany
+15055,%s: obiekt „this” nie należy do klasy Object
+15056,%s: obiekt „this” nie jest obiektem Function
+15057,%s: obiekt „this” nie jest obiektem String
+15058,%s: obiekt „this” nie jest obiektem Boolean
+15059,%s: obiekt „this” nie jest obiektem Date
+15060,%s: obiekt „this” nie jest obiektem Number
+15061,%s: obiekt „this” nie jest obiektem VBArray
+15062,%s: obiekt „this” nie jest obiektem JavaScript
+15063,%s: obiekt „this” nie jest obiektem Enumerator
+15064,%s: obiekt „this” nie jest obiektem RegExp
+15065,%s: nieprawidłowy argument
+15066,%s: argument nie jest klasy Object
+15067,%s: argument nie jest obiektem JavaScript
+15068,%s: argument nie jest obiektem Function
+15069,%s: argument nie jest obiektem VBArray
+15070,%s: argument jest pusty lub niezdefiniowany
+15071,%s: argument nie jest klasy Object i nie jest pusty
+15072,%s: argument nie ma prawidłowej właściwości „length”
+15073,%s: oczekiwano obiektu tablicy lub argumentów
+15074,Nieprawidłowy argument operacji „%s”: oczekiwano obiektu
+15075,Nieprawidłowy argument operacji „%s”: oczekiwano funkcji
+15076,Nieprawidłowy deskryptor właściwości „%s”
+15077,Nie można zdefiniować właściwości „%s”: obiekt nie jest rozszerzalny
+15078,Nie można ponownie zdefiniować niekonfigurowalnej właściwości „%s”
+15079,Nie można zdefiniować niezapisywalnej właściwości „%s”
+15080,Nie można zmodyfikować właściwości „%s”: wartość „length” jest niezapisywalna
+15081,Nie można zdefiniować właściwości „%s”
+15088,Wymagany argument %s w metodzie obiektu DataView nie został określony
+15090,Argument konstruktora obiektu DataView %s jest nieprawidłowy
+15091,Funkcja „%s” ma nieprawidłową sygnaturę i nie można jej wywołać
+15092,Właściwość „%s” ma nieprawidłową sygnaturę i nie można uzyskać do niej dostępu
+15093,Obiekt z nazwą runtimeclass %s mający interfejs domyślny Windows.Foundation.IPropertyValue nie jest obsługiwany jako typ parametru wejściowego
+15094,Obiekt z interfejsem Windows.Foundation.IPropertyValue mający nazwę runtimeclass %s nie jest obsługiwany jako parametr wyjściowy
+15096,%s: „this” nie jest obiektem Inspectable
+15097,%s: nie można przekonwertować argumentu na typ „char”
+15098,%s: nie można przekonwertować argumentu na typ „GUID”
+15099,%s: nie można przekonwertować zwróconej wartości na obiekt IInspectable
+15100,Nie można przekonwertować obiektu na strukturę: w obiekcie brakuje oczekiwanej właściwości „%s”
+15101,Nie odnaleziono typu „%s”
+15102,%s: wywołano funkcję ze zbyt małą liczbą argumentów
+15103,%s: typ nie umożliwia konstrukcji
+15104,Nie można przekonwertować wartości na obiekt PropertyValue: wartość %s nie jest obsługiwana przez obiekt PropertyValue
+15105,Nie można przekonwertować wartości na obiekt IInspectable: wartość %s nie jest obsługiwana przez obiekt IInspectable
+15108,%s: obiekt Inspectable „this” został zwolniony i nie można uzyskać do niego dostępu
+15110,Typ obiektu „this” jest inny niż oczekiwany: %s
+15112,%s: wystąpił nieoczekiwany błąd podczas próby uzyskania informacji o metadanych
+32812,Podana data jest niedostępna w kalendarzu ustawionym według bieżących ustawień regionalnych
diff --git a/src/sentry/data/error-locale/pt-BR.txt b/src/sentry/data/error-locale/pt-BR.txt
new file mode 100644
index 00000000000000..16e1a1b290f5da
--- /dev/null
+++ b/src/sentry/data/error-locale/pt-BR.txt
@@ -0,0 +1,289 @@
+5,Chamada de procedimento ou argumento inválido
+6,Estouro
+7,Memória insuficiente
+9,Subscrito fora do intervalo
+10,Matriz fixa ou temporariamente bloqueada
+11,Divisão por zero
+13,Tipos incompatíveis
+14,Espaço insuficiente para cadeias
+17,Não é possível executar a operação solicitada
+28,Espaço insuficiente na pilha
+35,Sub ou Function não definida
+48,Erro ao carregar DLL
+51,Erro interno
+52,Nome ou número de arquivo inválido
+53,Arquivo não encontrado
+54,Modo de arquivo inválido
+55,O arquivo já está aberto
+57,Erro de E/S do dispositivo
+58,O arquivo já existe
+61,Disco cheio
+62,Final do arquivo ultrapassado
+67,Número excessivo de arquivos
+68,Dispositivo não disponível
+70,Permissão negada
+71,O disco não está pronto
+74,Não é possível renomear com unidade diferente
+75,Erro de acesso ao caminho/arquivo
+76,Caminho não encontrado
+91,A variável do objeto ou a variável do bloco 'With' não foi definida
+92,Loop For não inicializado
+94,Uso inválido de Null
+322,Não é possível criar o arquivo temporário necessário
+424,Objeto necessário
+429,O servidor de automação não pode criar objeto
+430,A classe não dá suporte para automação
+432,Nome de arquivo ou classe não encontrado durante a operação de automação
+438,O objeto não dá suporte para a propriedade ou método
+440,Erro de automação
+445,O objeto não oferece suporte à ação
+446,O objeto não dá suporte para argumentos nomeados
+447,O objeto não dá suporte para a configuração de localidade atual
+448,Argumento nomeado não encontrado
+449,O argumento não é opcional
+450,Número de argumentos incorreto ou atribuição de propriedade inválida
+451,O objeto não é uma coleção
+453,Função de DLL especificada não encontrada
+458,A variável usa um tipo de automação sem suporte no JavaScript
+462,O servidor remoto não existe ou não está disponível
+501,Não é possível atribuir a uma variável
+502,O objeto não é seguro para scripts
+503,O objeto não é seguro para inicialização
+504,O objeto não é seguro para criação
+507,Exceção
+1001,Memória insuficiente
+1002,Erro de sintaxe
+1003,':' esperado
+1004,';' esperado
+1005,'(' esperado
+1006,')' esperado
+1007,']' esperado
+1008,'{' esperado
+1009,'}' esperado
+1010,Identificador esperado
+1011,'=' esperado
+1012,'/' esperado
+1013,Número inválido
+1014,Caractere inválido
+1015,Constante de cadeia não finalizada
+1016,Comentário não finalizado
+1018,Instrução 'return' fora da função
+1019,Não pode haver 'break' fora de loop
+1020,Não pode haver 'continue' fora de loop
+1023,Dígito hexadecimal esperado
+1024,'while' esperado
+1025,Rótulo redefinido
+1026,Rótulo não encontrado
+1027,'default' pode aparecer somente uma vez em uma instrução 'switch'
+1028,Identificador, cadeia ou número esperado
+1029,'@end' esperado
+1030,Compilação condicional desativada
+1031,Constante esperada
+1032,'@' esperado
+1033,'catch' esperado
+1034,'var' esperado
+1035,O 'descarte' deve ser seguido por uma expressão na mesma linha de origem
+1037,As instruções 'wit'h não são permitidas no modo estrito
+1038,Nomes de parâmetro formais duplicados não permitidos no modo estrito
+1039,Literais numéricos octais e caracteres de escape não permitidos no modo estrito
+1041,Uso inválido de 'eval' no modo estrito
+1042,Uso inválido de 'arguments' no modo estrito
+1045,Execução de exclusão na expressão não permitida no modo estrito
+1046,Várias definições de uma propriedade não permitidas no modo estrito
+1047,No modo estrito, as declarações de função não podem ser aninhadas dentro de uma instrução ou bloco. Elas só podem aparecer no nível superior ou diretamente dentro de um corpo de função.
+1048,É inválido usar uma palavra-chave para um identificador
+1049,É inválido usar uma palavra reservada para o futuro para um identificador
+1050,É inválido usar uma palavra reservada para o futuro para um identificador. O nome do identificador é reservado no modo estrito.
+1051,As funções setter devem ter um argumento
+4096,Erro de compilação do JavaScript
+4097,Erro em tempo de execução do JavaScript
+4098,Erro em tempo de execução desconhecido
+5000,Não é possível atribuir a 'this'
+5001,Número esperado
+5002,Função esperada
+5003,Não é possível atribuir ao resultado de uma função
+5004,Não é possível indexar o objeto
+5005,Cadeia esperada
+5006,Objeto de data esperado
+5007,Objeto esperado
+5008,Lado esquerdo inválido em atribuição
+5009,Identificador não definido
+5010,Booliano esperado
+5011,Não é possível executar o código a partir de um script liberado
+5012,Membro de objeto esperado
+5013,VBArray esperado
+5014,Objeto JavaScript esperado
+5015,Objeto enumerador esperado
+5016,Objeto de expressão normal esperado
+5017,Erro de sintaxe em expressão normal
+5018,Quantificador inesperado
+5019,']' esperado na expressão normal
+5020,')' esperado na expressão normal
+5021,Intervalo inválido no conjunto de caracteres
+5022,Exceção descartada e não capturada
+5023,A função não possui um objeto protótipo válido
+5024,O URI a ser codificado contém um caractere inválido
+5025,O URI a ser decodificado não é uma codificação válida
+5026,O número de dígitos fracionários está fora do intervalo
+5027,A precisão está fora do intervalo
+5028,Objeto Array ou argumentos esperados
+5029,O tamanho da matriz deve ser um número inteiro positivo finito
+5030,Um número positivo finito deve ser atribuído ao tamanho da matriz
+5031,Objeto Array esperado
+5034,Não há suporte para referência circular no argumento de valor
+5035,Argumento substituto inválido
+5038,Lista de argumentos muito grande para aplicação
+5039,Redeclaração da propriedade const
+5041,Membro de objeto não configurável
+5042,Variável não definida no modo estrito
+5043,O acesso à propriedade 'caller' de uma função ou objeto de argumentos não é permitido no modo estrito
+5044,O acesso à propriedade 'callee' de um objeto de argumentos não é permitido no modo estrito
+5045,A atribuição às propriedades somente leitura não permitida no modo estrito
+5046,Não é possível criar propriedade para um objeto não extensível
+5047,Objeto esperado
+5048,Objeto esperado
+5049,Objeto esperado
+5050,Objeto esperado
+5051,Função esperada
+5052,Função esperada
+5053,A propriedade não pode ter acessadores e um valor
+5054,'this' é nulo ou não definido
+5055,Objeto esperado
+5056,Função esperada
+5057,Cadeia de caracteres esperada
+5058,Booliano esperado
+5059,Data esperada
+5060,Número esperado
+5061,VBArray esperado
+5062,Objeto JavaScript esperado
+5063,Objeto enumerador esperado
+5064,Objeto RegExp esperado
+5065,Argumento de função inválido
+5066,Objeto esperado
+5067,Objeto JavaScript esperado
+5068,Função esperada
+5069,VBArray esperado
+5070,Objeto esperado
+5071,Objeto esperado
+5072,Propriedade 'length' inválida
+5073,Objeto Array ou arguments esperado
+5074,Operando Inválido
+5075,Operando Inválido
+5076,Descritor de propriedade inválido
+5077,Não é possível definir a propriedade: o objeto não é extensível
+5078,Não é possível redefinir propriedade não configurável
+5079,Não é possível modificar propriedade não gravável
+5080,Não é possível modificar a propriedade: 'length' não é gravável
+5081,Não é possível definir a propriedade
+5082,O argumento do construtor de matriz digitada é inválido
+5083,'este' não é um objeto de matriz digitada
+5084,Deslocamento/tamanho inválido ao criar matriz digitada
+5085,Valor inicial/final inválido no método de submatriz da matriz digitada
+5086,Origem inválida no conjunto de matrizes digitadas
+5087,'este' não é um objeto DataView
+5088,Argumentos inválidos no DataView
+5089,Acesso à operação DataView além do tamanho de buffer especificado
+5090,Argumentos inválidos no DataView
+5091,assinatura de função inválida
+5092,assinatura de propriedade inválida
+5093,tipo de parâmetro de entrada inválido
+5094,parâmetro de saída inválido
+5095,O acesso à propriedade 'arguments' de uma função não é permitido no modo estrito
+5096,Objeto Inspecionável esperado
+5097,Não foi possível converter argumento em tipo 'char'
+5098,Não foi possível converter argumento em tipo 'GUID'
+5099,IInspectable esperado
+5100,Não foi possível converter objeto em struct: propriedade esperada ausente do objeto
+5101,tipo desconhecido
+5102,Função chamada com poucos argumentos
+5103,O tipo não é construível
+5104,Não foi possível converter valor em PropertyValue: Tipo sem suporte de PropertyValue
+5105,Não foi possível converter valor em IInspectable: Tipo sem suporte de IInspectable
+5106,Não foi possível converter Data em Windows.Foundation.DateTime: valor fora do intervalo válido
+5107,Não foi possível converter valor em Windows.Foundation.TimeSpan: valor fora do intervalo válido
+5108,Acesso inválido a Objeto Inspecionável já liberado
+5109,Não é possível liberar Objeto Inspecionável já liberado
+5110,'isto' não é do tipo esperado
+5111,Comprimento e tamanho inválidos especificados para a matriz
+5112,Ocorreu uma falha inesperada ao tentar obter informações de metadados
+5200,O status é 'erro', mas getResults não retornou um erro
+5201,Parâmetro de status ausente ou inválido repassado para o manipulador concluído
+5202,Parâmetro de remetente ausente ou inválido repassado para o manipulador concluído
+6000,Infinito
+6001,-Infinito
+10438,O objeto não oferece suporte à propriedade ou método '%s'
+10449,O argumento para a função '%s' não é opcional
+15001,'%s' não é um número
+15002,'%s' não é uma função
+15004,'%s' não é um objeto que pode ser indexado
+15005,'%s' não é uma cadeia
+15006,'%s' não é um objeto de data
+15007,'%s' é nulo ou não é um objeto
+15008,Não é possível atribuir a '%s'
+15009,'%s' não está definido
+15010,'%s' não é um booliano
+15012,Não é possível excluir '%s'
+15013,'%s' não é um VBArray
+15014,'%s' não é um objeto JavaScript
+15015,'%s' não é um objeto enumerador
+15016,'%s' não é um objeto de expressão regular
+15028,%s não é um objeto Array ou arguments
+15031,%s não é um objeto Array
+15036,O atributo '%s' no descritor de propriedade não pode ser definido como 'true' neste objeto
+15037,O atributo '%s' no descritor de propriedade não pode ser definido como 'false' neste objeto
+15039,Redeclaração de const '%s'
+15041,Execução de exclusão em '%s' não permitida no modo estrito
+15047,Não é possível definir a propriedade '%s' de referência indefinida ou nula
+15048,Não é possível obter a propriedade '%s' de referência indefinida ou nula
+15049,Não é possível excluir a propriedade '%s' de referência indefinida ou nula
+15050,Não é possível acessar a propriedade '%s': tipo 'VarDate' não oferece suporte a propriedades definidas pelo usuário
+15051,O valor da propriedade '%s' não é um objeto de Função
+15052,O valor da propriedade '%s' é nulo ou não definido; não é um objeto de Função
+15054,%s: 'this' é nulo ou não definido
+15055,%s: 'this' não é um Objeto
+15056,%s: 'this' não é um objeto de Função
+15057,%s: 'this' não é um objeto de Cadeia de Caracteres
+15058,%s: 'this' não é um objeto Booliano
+15059,%s: 'this' não é um objeto de Data
+15060,%s: 'this' não é um objeto de Número
+15061,%s: 'this' não é um objeto VBArray
+15062,%s: 'this' não é um objeto JavaScript
+15063,%s: 'this' não é um objeto Enumerador
+15064,%s: 'this' não é um objeto RegExp
+15065,%s: argumento inválido
+15066,%s: argumento não é um Objeto
+15067,%s: argumento não é um objeto JavaScript
+15068,%s: argumento não é um objeto de Função
+15069,%s: argumento não é um objeto VBArray
+15070,%s: argumento é nulo ou não definido
+15071,%s: argumento não é um Objeto e não é nulo
+15072,%s: argumento não tem uma propriedade 'length' válida
+15073,%s: objeto Array ou arguments esperado
+15074,Operando inválido para '%s': Objeto esperado
+15075,Operando inválido para '%s': Função esperada
+15076,Descritor inválido para propriedade '%s'
+15077,Não é possível definir a propriedade '%s': o objeto não é extensível
+15078,Não é possível redefinir a propriedade não configurável '%s'
+15079,Não é possível modificar a propriedade não gravável '%s'
+15080,Não é possível modificar a propriedade '%s': 'length' não é gravável
+15081,Não é possível definir a propriedade '%s'
+15088,O argumento necessário %s no método DataView não está especificado
+15090,O argumento do construtor DataView %s é inválido
+15091,A função '%s' tem uma assinatura inválida e não pode ser chamada
+15092,A propriedade '%s' tem uma assinatura inválida e não pode ser acessada
+15093,O runtimeclass %s que tem Windows.Foundation.IPropertyValue como interface padrão não tem suporte como tipo de parâmetro de entrada
+15094,O objeto com interface Windows.Foundation.IPropertyValue que tem runtimeclass chamado %s não tem suporte como parâmetro de saída
+15096,%s: 'this' não é um objeto Inspecionável
+15097,%s: não foi possível converter argumento em tipo 'char'
+15098,%s: não foi possível converter argumento em tipo 'GUID'
+15099,%s: não foi possível converter valor de retorno em IInspectable
+15100,Não foi possível converter objeto em struct: propriedade esperada '%s' ausente do objeto
+15101,Tipo '%s' não encontrado
+15102,%s: função chamada com poucos argumentos
+15103,%s: o tipo não é construível
+15104,Não foi possível converter valor em PropertyValue: %s sem suporte de PropertyValue
+15105,Não foi possível converter valor em IInspectable: %s sem suporte de IInspectable
+15108,%s: O objeto inspecionável 'this' foi liberado e não pode ser acessado
+15110,'isto' não é do tipo esperado: %s
+15112,%s: ocorreu uma falha inesperada ao tentar obter informações de metadados
+32812,A data especificada não está disponível no calendário da localidade atual
diff --git a/src/sentry/data/error-locale/pt-PT.txt b/src/sentry/data/error-locale/pt-PT.txt
new file mode 100644
index 00000000000000..3ffd3ef7d1aca6
--- /dev/null
+++ b/src/sentry/data/error-locale/pt-PT.txt
@@ -0,0 +1,289 @@
+5,Chamada ou argumento de procedimento inválido
+6,Excesso de capacidade
+7,Memória esgotada
+9,Formato inferior à linha fora do intervalo
+10,Esta matriz é constante ou está temporariamente bloqueada
+11,Divisão por zero
+13,Tipo incorreto
+14,Espaço insuficiente para cadeias
+17,Não é possível efetuar operação pedida
+28,Espaço insuficiente na pilha
+35,Sub ou Function não definidas
+48,Erro ao carregar a DLL
+51,Erro interno
+52,Nome ou número de ficheiro incorreto
+53,Ficheiro não encontrado
+54,Modo de ficheiro incorreto
+55,Ficheiro já está aberto
+57,Erro de E/S no dispositivo
+58,Ficheiro já existente
+61,Disco cheio
+62,O comando Input ultrapassou o final do ficheiro
+67,Demasiados ficheiros
+68,Dispositivo não disponível
+70,Permissão negada
+71,O disco não está preparado
+74,Não é possível mudar o nome para uma unidade diferente
+75,Erro de acesso a caminho/ficheiro
+76,Caminho não encontrado
+91,Variável de objeto ou variável de bloco With não definidos
+92,Loop com For não inicializado
+94,Utilização incorreta de Null
+322,Não é possível criar o ficheiro temporário necessário
+424,Objeto necessário
+429,O servidor Automation não consegue criar o objeto
+430,Class não suporta Automation
+432,Nome de ficheiro ou classe não encontrado durante a operação Automation
+438,O objeto não suporta esta propriedade ou método
+440,Erro de Automation
+445,O objeto não suporta esta ação
+446,O objeto não suporta argumentos com nomes
+447,O objeto não suporta a definição de local atual
+448,Nome de argumento não encontrado
+449,O argumento não é opcional
+450,Número errado de argumentos ou atribuição inválida de propriedade
+451,O objeto não é uma coleção
+453,A função DLL especificada não foi encontrada
+458,A variável utiliza um tipo de Automation não suportado em JavaScript
+462,O servidor remoto não existe ou não está disponível
+501,Não é possível atribuir um valor à variável
+502,Não é seguro processar o script do objeto
+503,Não é seguro iniciar o objeto
+504,Não é seguro criar objeto
+507,Ocorreu uma exceção
+1001,Memória esgotada
+1002,Erro de sintaxe
+1003,Caráter ':' esperado
+1004,Caráter ';' esperado
+1005,Caráter '(' esperado
+1006,Caráter ')' esperado
+1007,Caráter ']' esperado
+1008,Caráter '{' esperado
+1009,Caráter '}' esperado
+1010,Identificador esperado
+1011,Caráter '=' esperado
+1012,Caráter '/' esperado
+1013,Número inválido
+1014,Caráter inválido
+1015,Constante de cadeia não terminada
+1016,Comentário não terminado
+1018,Instrução 'return' fora da função
+1019,Não é permitido 'break' fora do loop
+1020,Não é permitido 'continue' fora do loop
+1023,Dígito hexadecimal esperado
+1024,'while' esperado
+1025,Rótulo redefinido
+1026,Rótulo não encontrado
+1027,'default' só pode aparecer uma vez numa instrução 'switch'
+1028,Identificador, cadeia ou número esperado
+1029,Caráter '@end' esperado
+1030,A compilação condicional está desativada
+1031,Constante esperada
+1032,Caráter '@' esperado
+1033,'catch' esperado
+1034,'var' esperado
+1035,a 'devolução de uma exceção' deverá ser seguida de uma expressão na mesma linha de código original
+1037,declarações 'com' não são permitidas no modo estrito
+1038,Não são permitidos nomes de parâmetro formais duplicados no modo estrito
+1039,Não são permitidos literais numéricos octais e carateres de escape no modo estrito
+1041,Utilização inválida de 'eval' no modo estrito
+1042,Utilização inválida de 'arguments' no modo estrito
+1045,Chamada de eliminação com expressão não permitida no modo estrito
+1046,Múltiplas definições de uma propriedade não permitidas no modo estrito
+1047,No modo restrito, não é possível aninhar declarações de função dentro de uma instrução ou bloco. Só podem aparecer no nível superior ou diretamente dentro do corpo de uma função.
+1048,A utilização de uma palavra-chave para um identificador é inválida
+1049,A utilização de uma palavra reservada futura para um identificador é inválida
+1050,A utilização de uma palavra reservada futura para um identificador é inválida. O nome do identificador está reservado no modo restrito.
+1051,As funções setter têm de ter um argumento
+4096,Erro de compilação de JavaScript
+4097,Erro de runtime de JavaScript
+4098,Erro em tempo de execução desconhecido
+5000,Não é possível atribuir um valor a 'this'
+5001,Número esperado
+5002,Função esperada
+5003,Não é permitido atribuir um valor a um resultado de função
+5004,Não é possível indexar objeto
+5005,Cadeia esperada
+5006,Objeto Date esperado
+5007,Objeto esperado
+5008,Parte esquerda inválida na atribuição
+5009,Identificador não definido
+5010,Booleano esperado
+5011,Não é possível executar códigos a partir de um script livre
+5012,Membro de objeto esperado
+5013,VBArray esperado
+5014,Objeto JavaScript esperado
+5015,Objeto Enumerator esperado
+5016,Objeto Regular Expression esperado
+5017,Erro de sintaxe na expressão normal
+5018,Quantificador inesperado
+5019,Caráter ']' esperado em expressão normal
+5020,Caráter ')' esperado em expressão normal
+5021,Intervalo inválido no conjunto de carateres
+5022,Foi devolvida uma exceção mas desconhece-se a sua origem
+5023,A função não tem um objeto protótipo válido
+5024,O URI a ser codificado contém um caráter inválido
+5025,O URI a ser descodificado não possui uma codificação válida
+5026,O número de dígitos fracionais está fora do intervalo
+5027,A precisão está fora do intervalo
+5028,Objeto 'Array' ou 'arguments' esperado
+5029,O comprimento da matriz tem de ser um inteiro finito positivo
+5030,O comprimento da matriz tem de ter atribuído um número finito positivo
+5031,Objeto 'Array' esperado
+5034,A referência circular no argumento 'value' não é suportada
+5035,Argumento 'replacer' inválido
+5038,Lista de argumentos demasiado grande para aplicar
+5039,Nova declaração da propriedade const
+5041,Membro de objeto não configurável
+5042,Variável não definida no modo estrito
+5043,Acesso à propriedade 'caller' de um objeto de função ou de argumentos não permitido no modo estrito
+5044,Acesso à propriedade 'callee' de um objeto de argumentos não permitido no modo estrito
+5045,Atribuição a propriedades só de leitura não permitida no modo estrito
+5046,Não é possível criar a propriedade para um objeto não extensível
+5047,Objeto esperado
+5048,Objeto esperado
+5049,Objeto esperado
+5050,Objeto esperado
+5051,Função esperada
+5052,Função esperada
+5053,A propriedade não pode ter simultaneamente acessores e um valor
+5054,'this' é nulo ou não definido
+5055,Objeto esperado
+5056,Função esperada
+5057,Cadeia esperada
+5058,Booleano esperado
+5059,Data esperada
+5060,Número esperado
+5061,VBArray esperado
+5062,Objeto JavaScript esperado
+5063,Objeto Enumerator esperado
+5064,Objeto RegExp esperado
+5065,Argumento de função inválido
+5066,Objeto esperado
+5067,Objeto JavaScript esperado
+5068,Função esperada
+5069,VBArray esperado
+5070,Objeto esperado
+5071,Objeto esperado
+5072,Propriedade 'length' inválida
+5073,Objeto 'Array' ou 'arguments' esperado
+5074,Operando inválido
+5075,Operando inválido
+5076,Descritor de propriedade inválido
+5077,Não é possível definir a propriedade: o objeto não é extensível
+5078,Não é possível redefinir uma propriedade não configurável
+5079,Não é possível alterar uma propriedade não gravável
+5080,Não é possível alterar a propriedade: 'length' não é gravável
+5081,Não é possível definir a propriedade
+5082,O argumento de construtor da matriz escrita é inválido
+5083,'this' não é um objeto da matriz escrita
+5084,Desvio/comprimento inválido ao criar a matriz escrita
+5085,Valor de início/fim inválido no método de submatriz da matriz escrita
+5086,Origem inválida no conjunto da matriz escrita
+5087,'this' não é um objeto DataView
+5088,Argumentos inválidos em DataView
+5089,Acesso da operação DataView para lá do tamanho da memória intermédia especificado
+5090,Argumentos inválidos em DataView
+5091,assinatura de função inválida
+5092,assinatura de propriedade inválida
+5093,tipo de parâmetro de entrada inválido
+5094,parâmetro de saída inválido
+5095,O acesso à propriedade 'arguments' de uma função não é permitido no modo restrito
+5096,Objeto Passível de Inspeção esperado
+5097,Não foi possível converter o argumento no tipo 'char'
+5098,Não foi possível converter o argumento no tipo 'GUID'
+5099,Era esperado IInspectable
+5100,Não foi possível converter o objeto em estrutura: falta uma propriedade esperada no objeto
+5101,Tipo desconhecido
+5102,Função chamada com argumentos insuficientes
+5103,O tipo não é estruturável
+5104,Não foi possível converter o valor em PropertyValue: tipo não suportado por PropertyValue
+5105,Não foi possível converter o valor em IInspectable: tipo não suportado por IInspectable
+5106,Não foi possível converter Date em Windows.Foundation.DateTime: valor fora do intervalo válido
+5107,Não foi possível converter o valor em Windows.Foundation.TimeSpan: valor fora do intervalo válido
+5108,Acesso inválido ao Objeto Passível de Inspeção já libertado
+5109,Não é possível libertar o Objeto Passível de Inspeção já libertado
+5110,'this' não tem o tipo esperado
+5111,Comprimento e tamanho inválidos especificados para a matriz
+5112,Ocorreu uma falha inesperada ao tentar obter informações de metadados
+5200,O estado é 'erro', mas getResults não devolveu um erro
+5201,Um parâmetro de estado inválido ou em falta foi transmitido a um processador concluído
+5202,Um parâmetro de emissor inválido ou em falta foi transmitido a um processador concluído
+6000,Infinito
+6001,-Infinito
+10438,O objeto não suporta a propriedade nem o método '%s'
+10449,O argumento da função '%s' não é opcional
+15001,'%s' não é um número
+15002,'%s' não é uma função
+15004,'%s' não é um objeto indexável
+15005,'%s' não é uma cadeia
+15006,'%s' não é um objeto Date
+15007,'%s' é nulo ou não é um objeto
+15008,Não é possível atribuir a '%s'
+15009,'%s' não está definido
+15010,'%s' não é um booleano
+15012,Não é possível eliminar '%s'
+15013,'%s' não é um VBArray
+15014,'%s' não é um objeto JavaScript
+15015,'%s' não é um objeto Enumerator
+15016,'%s' não é um objeto Regular Expression
+15028,%s não é um objeto 'Array' ou 'arguments'
+15031,%s não é um objeto 'Array'
+15036,Não é possível definir o atributo '%s' do descritor de propriedades como 'true' neste objeto
+15037,Não é possível definir o atributo '%s' do descritor de propriedades como 'false' neste objeto
+15039,Nova declaração de const '%s'
+15041,Chamada de eliminação para '%s' não permitida no modo estrito
+15047,Não é possível definir a propriedade '%s' de uma referência não definida ou nula
+15048,Não é possível obter a propriedade '%s' de uma referência não definida ou nula
+15049,Não é possível eliminar a propriedade '%s' de uma referência não definida ou nula
+15050,Não é possível aceder à propriedade '%s': o tipo 'VarDate' não suporta propriedades definidas pelo utilizador
+15051,O valor da propriedade '%s' não é um objeto de Função
+15052,O valor da propriedade '%s' é nulo ou não definido, não um objeto de Função
+15054,%s: 'this' é nulo ou não definido
+15055,%s: 'this' não é um Objeto
+15056,%s: 'this' não é um objeto de Função
+15057,%s: 'this' não é um objeto de Cadeia
+15058,%s: 'this' não é um objeto Booleano
+15059,%s: 'this' não é um objeto de Data
+15060,%s: 'this' não é um objeto de Número
+15061,%s: 'this' não é um objeto VBArray
+15062,%s: 'this' não é um objeto JavaScript
+15063,%s: 'this' não é um objeto Enumerador
+15064,%s: 'this' não é um objeto RegExp
+15065,%s: argumento inválido
+15066,%s: o argumento não é um Objeto
+15067,%s: o argumento não é um objeto JavaScript
+15068,%s: o argumento não é um objeto de Função
+15069,%s: o argumento não é um objeto VBArray
+15070,%s: o argumento é nulo ou não definido
+15071,%s: o argumento não é um Objeto e não é nulo
+15072,%s: o argumento não tem uma propriedade 'length' válida
+15073,%s: Matriz ou objeto de argumentos esperados
+15074,Operando inválido para '%s': Objeto esperado
+15075,Operando inválido para '%s': Função esperada
+15076,Descritor inválido para a propriedade '%s'
+15077,Não é possível definir a propriedade '%s': objeto não é extensível
+15078,Não é possível redefinir uma propriedade não configurável '%s'
+15079,Não é possível alterar uma propriedade não gravável '%s'
+15080,Não é possível alterar a propriedade '%s': 'length' não é gravável
+15081,Não é possível definir a propriedade '%s'
+15088,O argumento necessário %s no método DataView não foi especificado
+15090,O argumento do construtor DataView %s é inválido
+15091,A função '%s' tem uma assinatura inválida e não pode ser chamada
+15092,A propriedade '%s' tem uma assinatura inválida e não pode ser acedida
+15093,O runtimeclass %s que tem Windows.Foundation.IPropertyValue como interface predefinida não é suportado como tipo de parâmetro de entrada
+15094,O objeto com a interface Windows.Foundation.IPropertyValue que tem o nome de runtimeclass %s não é suportado como parâmetro de saída
+15096,%s: 'this' não é um Objeto Passível de Inspeção
+15097,%s: não foi possível converter o argumento no tipo 'char'
+15098,%s: não foi possível converter o argumento no tipo 'GUID'
+15099,%s: não foi possível converter o valor de retorno em IInspectable
+15100,Não foi possível converter o objeto em estrutura: falta a propriedade esperada '%s' no objeto
+15101,O tipo '%s' não foi encontrado
+15102,%s: função chamada com argumentos insuficientes
+15103,%s: o tipo não é estruturável
+15104,Não foi possível converter o valor em PropertyValue: %s não é suportado por PropertyValue
+15105,Não foi possível converter o valor em IInspectable: %s não é suportado por IInspectable
+15108,%s: o objeto passível de inspeção 'this' foi libertado e não pode ser acedido
+15110,'this' não tem o tipo esperado: %s
+15112,%s: ocorreu uma falha inesperada ao tentar obter informações de metadados
+32812,A data especificada não está disponível no calendário da localização atual
diff --git a/src/sentry/data/error-locale/ro-RO.txt b/src/sentry/data/error-locale/ro-RO.txt
new file mode 100644
index 00000000000000..6580983d9b4a88
--- /dev/null
+++ b/src/sentry/data/error-locale/ro-RO.txt
@@ -0,0 +1,289 @@
+5,Invalid procedure call or argument
+6,Overflow
+7,Out of memory
+9,Subscript out of range
+10,This array is fixed or temporarily locked
+11,Division by zero
+13,Type mismatch
+14,Out of string space
+17,Can't perform requested operation
+28,Out of stack space
+35,Sub or Function not defined
+48,Error in loading DLL
+51,Internal error
+52,Bad file name or number
+53,File not found
+54,Bad file mode
+55,File already open
+57,Device I/O error
+58,File already exists
+61,Disk full
+62,Input past end of file
+67,Too many files
+68,Device unavailable
+70,Permission denied
+71,Disk not ready
+74,Can't rename with different drive
+75,Path/File access error
+76,Path not found
+91,Object variable or With block variable not set
+92,For loop not initialized
+94,Invalid use of Null
+322,Can't create necessary temporary file
+424,Object required
+429,Automation server can't create object
+430,Class doesn't support Automation
+432,File name or class name not found during Automation operation
+438,Object doesn't support this property or method
+440,Automation error
+445,Object doesn't support this action
+446,Object doesn't support named arguments
+447,Object doesn't support current locale setting
+448,Named argument not found
+449,Argument not optional
+450,Wrong number of arguments or invalid property assignment
+451,Object not a collection
+453,Specified DLL function not found
+458,Variable uses an Automation type not supported in JavaScript
+462,The remote server machine does not exist or is unavailable
+501,Cannot assign to variable
+502,Object not safe for scripting
+503,Object not safe for initializing
+504,Object not safe for creating
+507,An exception occurred
+1001,Out of memory
+1002,Syntax error
+1003,Expected ':'
+1004,Expected ';'
+1005,Expected '('
+1006,Expected ')'
+1007,Expected ']'
+1008,Expected '{'
+1009,Expected '}'
+1010,Expected identifier
+1011,Expected '='
+1012,Expected '/'
+1013,Invalid number
+1014,Invalid character
+1015,Unterminated string constant
+1016,Unterminated comment
+1018,'return' statement outside of function
+1019,Can't have 'break' outside of loop
+1020,Can't have 'continue' outside of loop
+1023,Expected hexadecimal digit
+1024,Expected 'while'
+1025,Label redefined
+1026,Label not found
+1027,'default' can only appear once in a 'switch' statement
+1028,Expected identifier, string or number
+1029,Expected '@end'
+1030,Conditional compilation is turned off
+1031,Expected constant
+1032,Expected '@'
+1033,Expected 'catch'
+1034,Expected 'var'
+1035,'throw' must be followed by an expression on the same source line
+1037,'with' statements are not allowed in strict mode
+1038,Duplicate formal parameter names not allowed in strict mode
+1039,Octal numeric literals and escape characters not allowed in strict mode
+1041,Invalid usage of 'eval' in strict mode
+1042,Invalid usage of 'arguments' in strict mode
+1045,Calling delete on expression not allowed in strict mode
+1046,Multiple definitions of a property not allowed in strict mode
+1047,In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.
+1048,The use of a keyword for an identifier is invalid
+1049,The use of a future reserved word for an identifier is invalid
+1050,The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.
+1051,Setter functions must have one argument
+4096,JavaScript compilation error
+4097,JavaScript runtime error
+4098,Unknown runtime error
+5000,Cannot assign to 'this'
+5001,Number expected
+5002,Function expected
+5003,Cannot assign to a function result
+5004,Cannot index object
+5005,String expected
+5006,Date object expected
+5007,Object expected
+5008,Invalid left-hand side in assignment
+5009,Undefined identifier
+5010,Boolean expected
+5011,Can't execute code from a freed script
+5012,Object member expected
+5013,VBArray expected
+5014,JavaScript object expected
+5015,Enumerator object expected
+5016,Regular Expression object expected
+5017,Syntax error in regular expression
+5018,Unexpected quantifier
+5019,Expected ']' in regular expression
+5020,Expected ')' in regular expression
+5021,Invalid range in character set
+5022,Exception thrown and not caught
+5023,Function does not have a valid prototype object
+5024,The URI to be encoded contains an invalid character
+5025,The URI to be decoded is not a valid encoding
+5026,The number of fractional digits is out of range
+5027,The precision is out of range
+5028,Array or arguments object expected
+5029,Array length must be a finite positive integer
+5030,Array length must be assigned a finite positive number
+5031,Array object expected
+5034,Circular reference in value argument not supported
+5035,Invalid replacer argument
+5038,Argument list too large to apply
+5039,Redeclaration of const property
+5041,Object member not configurable
+5042,Variable undefined in strict mode
+5043,Accessing the 'caller' property of a function or arguments object is not allowed in strict mode
+5044,Accessing the 'callee' property of an arguments object is not allowed in strict mode
+5045,Assignment to read-only properties is not allowed in strict mode
+5046,Cannot create property for a non-extensible object
+5047,Object expected
+5048,Object expected
+5049,Object expected
+5050,Object expected
+5051,Function expected
+5052,Function expected
+5053,Property cannot have both accessors and a value
+5054,'this' is null or undefined
+5055,Object expected
+5056,Function expected
+5057,String expected
+5058,Boolean expected
+5059,Date expected
+5060,Number expected
+5061,VBArray expected
+5062,JavaScript object expected
+5063,Enumerator object expected
+5064,RegExp object expected
+5065,Invalid function argument
+5066,Object expected
+5067,JavaScript object expected
+5068,Function expected
+5069,VBArray expected
+5070,Object expected
+5071,Object expected
+5072,Invalid 'length' property
+5073,Array or arguments object expected
+5074,Invalid Operand
+5075,Invalid Operand
+5076,Invalid property descriptor
+5077,Cannot define property: object is not extensible
+5078,Cannot redefine non-configurable property
+5079,Cannot modify non-writable property
+5080,Cannot modify property: 'length' is not writable
+5081,Cannot define property
+5082,Typed array constructor argument is invalid
+5083,'this' is not a typed array object
+5084,Invalid offset/length when creating typed array
+5085,Invalid begin/end value in typed array subarray method
+5086,Invalid source in typed array set
+5087,'this' is not a DataView object
+5088,Invalid arguments in DataView
+5089,DataView operation access beyond specified buffer length
+5090,Invalid arguments in DataView
+5091,invalid function signature
+5092,invalid property signature
+5093,invalid input parameter type
+5094,invalid ouput parameter
+5095,Accessing the 'arguments' property of a function is not allowed in strict mode
+5096,Inspectable Object expected
+5097,Could not convert argument to type 'char'
+5098,Could not convert argument to type 'GUID'
+5099,IInspectable expected
+5100,Could not convert object to struct: object missing expected property
+5101,Unknown type
+5102,Function called with too few arguments
+5103,Type is not constructible
+5104,Could not convert value to PropertyValue: Type not supported by PropertyValue
+5105,Could not convert value to IInspectable: Type not supported by IInspectable
+5106,Could not convert Date to Windows.Foundation.DateTime: value outside of valid range
+5107,Could not convert value to Windows.Foundation.TimeSpan: value outside of valid range
+5108,Invalid access to already released Inspectable Object
+5109,Cannot release already released Inspectable Object
+5110,'this' is not of the expected type
+5111,Illegal length and size specified for the array
+5112,An unexpected failure occurred while trying to obtain metadata information
+5200,Status is 'error', but getResults did not return an error
+5201,Missing or invalid status parameter passed to completed handler
+5202,Missing or invalid sender parameter passed to completed handler
+6000,Infinity
+6001,-Infinity
+10438,Object doesn't support property or method '%s'
+10449,Argument to the function '%s' is not optional
+15001,'%s' is not a number
+15002,'%s' is not a function
+15004,'%s' is not an indexable object
+15005,'%s' is not a string
+15006,'%s' is not a date object
+15007,'%s' is null or not an object
+15008,Cannot assign to '%s'
+15009,'%s' is undefined
+15010,'%s' is not a boolean
+15012,Cannot delete '%s'
+15013,'%s' is not a VBArray
+15014,'%s' is not a JavaScript object
+15015,'%s' is not an enumerator object
+15016,'%s' is not a regular expression object
+15028,%s is not an Array or arguments object
+15031,%s is not an Array object
+15036,'%s' attribute on the property descriptor cannot be set to 'true' on this object
+15037,'%s' attribute on the property descriptor cannot be set to 'false' on this object
+15039,Redeclaration of const '%s'
+15041,Calling delete on '%s' is not allowed in strict mode
+15047,Unable to set property '%s' of undefined or null reference
+15048,Unable to get property '%s' of undefined or null reference
+15049,Unable to delete property '%s' of undefined or null reference
+15050,Unable to access property '%s': type 'VarDate' does not support user-defined properties
+15051,The value of the property '%s' is not a Function object
+15052,The value of the property '%s' is null or undefined, not a Function object
+15054,%s: 'this' is null or undefined
+15055,%s: 'this' is not an Object
+15056,%s: 'this' is not a Function object
+15057,%s: 'this' is not a String object
+15058,%s: 'this' is not a Boolean object
+15059,%s: 'this' is not a Date object
+15060,%s: 'this' is not a Number object
+15061,%s: 'this' is not a VBArray object
+15062,%s: 'this' is not a JavaScript object
+15063,%s: 'this' is not an Enumerator object
+15064,%s: 'this' is not a RegExp object
+15065,%s: invalid argument
+15066,%s: argument is not an Object
+15067,%s: argument is not a JavaScript object
+15068,%s: argument is not a Function object
+15069,%s: argument is not a VBArray object
+15070,%s: argument is null or undefined
+15071,%s: argument is not an Object and is not null
+15072,%s: argument does not have a valid 'length' property
+15073,%s: Array or arguments object expected
+15074,Invalid operand to '%s': Object expected
+15075,Invalid operand to '%s': Function expected
+15076,Invalid descriptor for property '%s'
+15077,Cannot define property '%s': object is not extensible
+15078,Cannot redefine non-configurable property '%s'
+15079,Cannot modify non-writable property '%s'
+15080,Cannot modify property '%s': 'length' is not writable
+15081,Cannot define property '%s'
+15088,Required argument %s in DataView method is not specified
+15090,DataView constructor argument %s is invalid
+15091,The function '%s' has an invalid signature and cannot be called
+15092,The property '%s' has an invalid signature and cannot be accessed
+15093,The runtimeclass %s that has Windows.Foundation.IPropertyValue as default interface is not supported as input parameter type
+15094,The object with interface Windows.Foundation.IPropertyValue that has runtimeclass name %s is not supported as out parameter
+15096,%s: 'this' is not an Inspectable Object
+15097,%s: could not convert argument to type 'char'
+15098,%s: could not convert argument to type 'GUID'
+15099,%s: could not convert return value to IInspectable
+15100,Could not convert object to struct: object missing expected property '%s'
+15101,Type '%s' not found
+15102,%s: function called with too few arguments
+15103,%s: type is not constructible
+15104,Could not convert value to PropertyValue: %s not supported by PropertyValue
+15105,Could not convert value to IInspectable: %s not supported by IInspectable
+15108,%s: The Inspectable object 'this' is released and cannot be accessed
+15110,'this' is not of expected type: %s
+15112,%s: an unexpected failure occurred while trying to obtain metadata information
+32812,The specified date is not available in the current locale's calendar
diff --git a/src/sentry/data/error-locale/ru-RU.txt b/src/sentry/data/error-locale/ru-RU.txt
new file mode 100644
index 00000000000000..07a3ee02d18c04
--- /dev/null
+++ b/src/sentry/data/error-locale/ru-RU.txt
@@ -0,0 +1,289 @@
+5,Недопустимый вызов или аргумент процедуры
+6,Переполнение
+7,Недостаточно памяти
+9,Индекс выходит за пределы допустимого диапазона
+10,Массив имеет фиксированную длину или временно блокирован
+11,Деление на 0
+13,Несоответствие типа
+14,Недостаточно памяти для строки
+17,Не удается выполнить требуемую операцию
+28,Недостаточно места в стеке
+35,Процедура Sub или Function не определена
+48,Ошибка при загрузке DLL
+51,Внутренняя ошибка
+52,Недопустимое имя или номер файла
+53,Файл не найден
+54,Недопустимый режим файла
+55,Файл уже открыт
+57,Ошибка устройства ввода/вывода
+58,Файл уже существует
+61,Диск переполнен
+62,Ввод данных за пределами файла
+67,Слишком много файлов
+68,Нет доступа к устройству
+70,Разрешение отклонено
+71,Диск не готов
+74,Невозможно переименование с другим именем диска
+75,Ошибка доступа к файлу или каталогу
+76,Путь не найден
+91,Объектная переменная или переменная блока With не задана
+92,Цикл For не инициализирован
+94,Недопустимое использование Null
+322,Невозможно создание требуемого временного файла
+424,Требуется объект
+429,Невозможно создание объекта сервером программирования объектов
+430,Класс не поддерживает программирование объектов
+432,Не найдено имя файла или класса при операции программирования объектов
+438,Объект не поддерживает это свойство или метод
+440,Ошибка программирования объектов
+445,Команда не поддерживается объектом
+446,Объект не поддерживает именованные аргументы
+447,Объект не поддерживает текущую национальную настройку
+448,Именованный аргумент не найден
+449,Обязательный аргумент
+450,Недопустимое число аргументов или присвоение значения свойства
+451,Объект не является семейством
+453,Указанная функция DLL не найдена
+458,Переменная использует не поддерживаемый в JavaScript тип программирования объектов
+462,Компьютер удаленного сервера не существует или недоступен
+501,Присвоение значения переменной невозможно
+502,Применение объекта в сценариях небезопасно
+503,Инициализация объекта небезопасна
+504,Создание объекта небезопасно
+507,Возникло исключение
+1001,Недостаточно памяти
+1002,Синтаксическая ошибка
+1003,Предполагается наличие ':'
+1004,Предполагается наличие ';'
+1005,Предполагается наличие '('
+1006,Предполагается наличие ')'
+1007,Предполагается наличие ']'
+1008,Предполагается наличие '{'
+1009,Предполагается наличие '}'
+1010,Предполагается наличие идентификатора
+1011,Предполагается наличие '='
+1012,Предполагается наличие '/'
+1013,Недопустимое число
+1014,Недопустимый знак
+1015,Незавершенная строковая константа
+1016,Незавершенная строка комментария
+1018,Инструкция 'return' вне функции
+1019,Инструкция 'break' не может находиться вне блока loop
+1020,Инструкция 'continue' не может находиться вне блока loop
+1023,Предполагается шестнадцатеричное число
+1024,Предполагается наличие 'while'
+1025,Метка переопределена
+1026,Метка не найдена
+1027,Инструкция 'default' может присутствовать в конструкции 'switch' только один раз
+1028,Предполагается наличие идентификатора, строки или числа
+1029,Предполагается наличие '@end'
+1030,Условная компиляция выключена
+1031,Предполагается наличие константы
+1032,Предполагается наличие '@'
+1033,Ожидается 'catch'
+1034,Ожидается 'var'
+1035,"throw" требует после себя выражение на той же строке
+1037,операторы "with" запрещены в строгом режиме
+1038,Дублирование имен формальных параметров в строгом режиме запрещено
+1039,Восьмеричные численные литералы и escape-символы в строгом режиме запрещены
+1041,Недопустимое использование функции "eval" в строгом режиме
+1042,Недопустимое использование функции "arguments" в строгом режиме
+1045,Вызов удаления для выражения в строгом режиме запрещен
+1046,Использование нескольких определений свойства в строгом режиме запрещено
+1047,При использовании строгого режима объявления функций не могут располагаться внутри оператора или блока. Объявления функций могут располагаться только на верхнем уровне или непосредственно в теле функции.
+1048,Использование ключевых слов для идентификатора недопустимо
+1049,Использование зарезервированных слов для идентификатора недопустимо
+1050,Использование зарезервированных слов для идентификатора недопустимо. Имя идентификатора зарезервировано в строгом режиме.
+1051,У функций Setter должен быть один аргумент
+4096,Ошибка компиляции JavaScript
+4097,Ошибка выполнения JavaScript
+4098,Неизвестная ошибка выполнения
+5000,Невозможно присвоение значения 'this'
+5001,Предполагается наличие числа
+5002,Предполагается наличие функции
+5003,Невозможно присвоение результату функции
+5004,Невозможно индексирование объекта
+5005,Предполагается наличие строки
+5006,Предполагается наличие объекта-даты
+5007,Предполагается наличие объекта
+5008,Недопустимая величина в левой части присвоения
+5009,Неопределенный идентификатор
+5010,Предполагается наличие логического значения
+5011,Не удается выполнить программу из освобожденного сценария
+5012,Предполагается наличие компонента объекта
+5013,Предполагается наличие VBArray
+5014,Ожидается объект JavaScript
+5015,Предполагается наличие объекта Enumerator
+5016,Предполагается наличие объекта регулярного выражения
+5017,Синтаксическая ошибка в регулярном выражении
+5018,Неизвестный числовой показатель
+5019,Предполагается наличие ']' в регулярном выражении
+5020,Предполагается наличие ')' в регулярном выражении
+5021,Недопустимый диапазон в наборе знаков
+5022,Исключение брошено и не поймано
+5023,Функция не имеет правильного объекта - прототипа
+5024,Обнаружен недопустимый знак при попытке кодирования URI
+5025,Обнаружена недопустимая кодировка при попытке декодирования URI
+5026,Количество цифр дробной части превышает допустимое значение
+5027,Точность представления превышает допустимую
+5028,Предполагается наличие объекта Array или arguments
+5029,Длина массива должна быть конечным положительным целым числом
+5030,Длине массива должно быть присвоено конечное положительное значение
+5031,Предполагается наличие объекта Array
+5034,Циклические ссылки не поддерживаются в аргументах значений
+5035,Недопустимый аргумент замены
+5038,Не удается применить список аргументов из-за слишком большого размера
+5039,Повторное объявление константного свойства
+5041,Ненастраиваемый элемент объекта
+5042,Неопределенная переменная в строгом режиме
+5043,Доступ к свойству "caller" функции или объекта "arguments" в строгом режиме запрещен
+5044,Доступ к свойству "callee" объекта "arguments" в строгом режиме запрещен
+5045,Присвоение значений свойствам только для чтения в строгом режиме запрещено
+5046,Не удается создать свойство для нерасширяемого объекта
+5047,Предполагается наличие объекта
+5048,Предполагается наличие объекта
+5049,Предполагается наличие объекта
+5050,Предполагается наличие объекта
+5051,Предполагается наличие функции
+5052,Предполагается наличие функции
+5053,Свойство не может одновременно иметь методы доступа и значение
+5054,объект "this" равен null или не определен
+5055,Предполагается наличие объекта
+5056,Предполагается наличие функции
+5057,Предполагается наличие строки
+5058,Предполагается наличие логического значения
+5059,Ожидается дата
+5060,Предполагается наличие числа
+5061,Предполагается наличие VBArray
+5062,Ожидается объект JavaScript
+5063,Предполагается наличие объекта Enumerator
+5064,Ожидается объект RegExp
+5065,Недопустимый аргумент функции
+5066,Предполагается наличие объекта
+5067,Ожидается объект JavaScript
+5068,Предполагается наличие функции
+5069,Предполагается наличие VBArray
+5070,Предполагается наличие объекта
+5071,Предполагается наличие объекта
+5072,Недопустимое свойство "length"
+5073,Предполагается наличие объекта Array или arguments
+5074,Недопустимый операнд
+5075,Недопустимый операнд
+5076,Недопустимый дескриптор свойства
+5077,Не удается определить свойство: объект не является расширяемым
+5078,Не удается переопределить ненастраиваемое свойство
+5079,Не удается изменить недоступное для записи свойство
+5080,Не удается изменить свойство: свойство "length" недоступно для записи
+5081,Не удается определить свойство
+5082,Аргумент конструктора типизированного массива недопустим
+5083,"this" не является объектом типизированного массива
+5084,Недопустимое смещение или длина при создании типизированного массива
+5085,Недопустимое значение начала или конца в методе подмассива типизированного массива
+5086,Недопустимый источник в наборе типизированного массива
+5087,"this" не является объектом DataView
+5088,Недопустимые аргументы в DataView
+5089,DataView пытается получить доступ к данным вне указанной длины буфера
+5090,Недопустимые аргументы в DataView
+5091,недопустимая подпись функции
+5092,недопустимая подпись свойства
+5093,недопустимый тип входного параметра
+5094,недопустимый тип выходного параметра
+5095,В строгом режиме невозможно получить доступ к свойству "arguments" функции
+5096,Ожидается объект, поддерживающий проверку
+5097,Не удалось преобразовать аргумент в тип "char"
+5098,Не удалось преобразовать аргумент в тип "GUID"
+5099,Ожидается IInspectable
+5100,Не удалось преобразовать объект в структуру: отсутствует ожидаемое свойство объекта
+5101,Неизвестный тип
+5102,Функция вызвана с недостаточным числом аргументов
+5103,Тип не является конструируемым
+5104,Не удалось преобразовать значение в PropertyValue: тип не поддерживается PropertyValue
+5105,Не удалось преобразовать значение в IInspectable: тип не поддерживается IInspectable
+5106,Не удалось преобразовать Date в Windows.Foundation.DateTime: значение находится вне допустимого диапазона
+5107,Не удалось преобразовать значение в Windows.Foundation.TimeSpan: значение находится вне допустимого диапазона
+5108,Недопустимая попытка доступа к уже освобожденному объекту, поддерживающему проверку
+5109,Невозможно освободить уже освобожденный объект, поддерживающий проверку
+5110,Ожидался другой тип объекта "this"
+5111,Заданы недопустимая длина и размер массива
+5112,Непредвиденный сбой при попытке получения метаданных
+5200,Состояние — "error", но команда getResults не возвратила ошибку
+5201,Параметр Status недопустим или не передан в обработчик Completed
+5202,Параметр Sender недопустим или не передан в обработчик Completed
+6000,Бесконечность
+6001,-Бесконечность
+10438,Объект не поддерживает свойство или метод "%s"
+10449,Аргумент функции "%s" является обязательным
+15001,"%s" не является числом
+15002,"%s" не является функцией
+15004,"%s" не является индексируемым объектом
+15005,"%s" не является строкой
+15006,"%s" не является объектом-датой
+15007,Значением "%s" является NULL или это значение не является объектом
+15008,Невозможно присвоить "%s"
+15009,"%s" не определено
+15010,"%s" не является логическим значением
+15012,Не удается удалить "%s"
+15013,"%s" — не VBArray
+15014,"%s" — не объект JavaScript
+15015,"%s" — не объект Enumerator
+15016,"%s" — не объект регулярного выражения
+15028,%s — не является ни объектом Array, ни объектом аргументов
+15031,%s — не объект Array
+15036,Для данного объекта атрибут "%s" дескриптора свойства не может иметь значение True
+15037,Для данного объекта атрибут "%s" дескриптора свойства не может иметь значение False
+15039,Повторное объявление константы "%s"
+15041,Вызов delete для "%s" в строгом режиме запрещен
+15047,Не удалось задать свойство "%s" ссылки, значение которой не определено или является NULL
+15048,Не удалось получить свойство "%s" ссылки, значение которой не определено или является NULL
+15049,Не удалось удалить свойство "%s" ссылки, значение которой не определено или является NULL
+15050,Не удалось получить доступ к свойству "%s": тип "VarDate" не поддерживает пользовательские свойства
+15051,Значение свойства "%s" не является объектом Function
+15052,Значением свойства "%s" или является NULL, или оно не определено, или не является объектом Function
+15054,%s: значением "this" является NULL или оно не определено
+15055,%s: "this" не является объектом
+15056,%s: "this" не является объектом Function
+15057,%s: "this" не является объектом String
+15058,%s: "this" не является объектом Boolean
+15059,%s: "this" не является объектом Date
+15060,%s: "this" не является объектом Number
+15061,%s: "this" не является объектом VBArray
+15062,%s: "this" не является объектом JavaScript
+15063,%s: "this" не является объектом Enumerator
+15064,%s: "this" не является объектом RegExp
+15065,%s: недопустимый аргумент
+15066,%s: аргумент не является объектом
+15067,%s: аргумент не является объектом JavaScript
+15068,%s: аргумент не является объектом Function
+15069,%s: аргумент не является объектом VBArray
+15070,%s: аргумент не определен или его значением является NULL
+15071,%s: аргумент не является объектом и его значением не является NULL
+15072,%s: у аргумента нет допустимого значения свойства "length"
+15073,%s: ожидается массив или объект аргументов
+15074,Недопустимый операнд "%s": ожидается объект
+15075,Недопустимый операнд "%s": ожидается функция
+15076,Недопустимый дескриптор свойства "%s"
+15077,Не удается определить свойство "%s": объект не является расширяемым
+15078,Не удается переопределить ненастраиваемое свойство "%s"
+15079,Не удается изменить недоступное для записи свойство "%s"
+15080,Не удается изменить свойство "%s": свойство "length" недоступно для записи
+15081,Не удается определить свойство "%s"
+15088,В методе DataView не указан обязательный аргумент %s
+15090,Аргумент %s конструктора DataView недопустим
+15091,Не удается вызвать функцию "%s", так как ее подпись недопустима
+15092,Не удается получить доступ к свойству "%s", так как его подпись недопустима
+15093,runtimeclass %s, использующий Windows.Foundation.IPropertyValue в качестве интерфейса по умолчанию, не поддерживается в качестве типа входного параметра
+15094,Объект с интерфейсом Windows.Foundation.IPropertyValue, который имеет имя runtimeclass %s, невозможно использовать в качестве выходного параметра
+15096,%s: "this" не является объектом, поддерживающим проверку
+15097,%s: не удалось преобразовать аргумент в тип "char"
+15098,%s: не удалось преобразовать аргумент в тип "GUID"
+15099,%s: не удалось вернуть значение в IInspectable
+15100,Не удалось преобразовать объект в структуру: отсутствует ожидаемое свойство объекта "%s"
+15101,Тип "%s" не найден
+15102,%s: функция вызвана с недостаточным числом аргументов
+15103,%s: тип не является конструируемым
+15104,Не удалось преобразовать значение в PropertyValue: %s не поддерживается PropertyValue
+15105,Не удалось преобразовать значение в IInspectable: %s не поддерживается IInspectable
+15108,%s: поддерживающий проверку объект "this" освобожден, и к нему невозможно получить доступ
+15110,Ожидался другой тип объекта "this": %s
+15112,%s: непредвиденный сбой при попытке получения метаданных
+32812,Указанная дата не найдена в текущем локальном календаре
diff --git a/src/sentry/data/error-locale/sk-SK.txt b/src/sentry/data/error-locale/sk-SK.txt
new file mode 100644
index 00000000000000..810b32c5381d2f
--- /dev/null
+++ b/src/sentry/data/error-locale/sk-SK.txt
@@ -0,0 +1,289 @@
+5,Neplatné volanie alebo neplatný argument procedúry
+6,Pretečenie
+7,Nedostatok pamäte
+9,Index je mimo rozsahu
+10,Pole je pevné alebo dočasne zamknuté
+11,Delenie nulou
+13,Rozdielne typy
+14,Nedostatok miesta v reťazci
+17,Požadovaná operácia sa nedá vykonať
+28,Nedostatok miesta v zásobníku
+35,Podprocedúra alebo funkcia sa nenašla
+48,Chyba pri načítavaní knižnice DLL
+51,Vnútorná chyba
+52,Nesprávny názov alebo nesprávne číslo súboru
+53,Súbor sa nenašiel
+54,Chybný režim súboru
+55,Súbor už je otvorený
+57,Vstupno-výstupná chyba zariadenia
+58,Súbor už existuje
+61,Disk je plný
+62,Vstup za koncom súboru
+67,Priveľa súborov
+68,Zariadenie nie je dostupné
+70,Prístup bol odmietnutý
+71,Disk nie je pripravený
+74,Nedá sa premenovať s použitím inej jednotky
+75,Chyba pri prístupe k ceste alebo k súboru
+76,Cesta sa nenašla
+91,Premenná objektu alebo bloku With nie je nastavená
+92,Cyklus For nie je inicializovaný
+94,Neplatné použitie konštanty Null
+322,Nemožno vytvoriť potrebný dočasný súbor
+424,Vyžaduje sa objekt
+429,Server automatizácie nemôže vytvoriť objekt
+430,Trieda nepodporuje automatizáciu
+432,Počas operácie automatizácie sa nenašiel názov súboru alebo triedy
+438,Objekt nepodporuje túto vlastnosť alebo metódu
+440,Chyba automatizácie
+445,Objekt nepodporuje túto akciu
+446,Objekt nepodporuje pomenované argumenty
+447,Objekt nepodporuje miestne nastavenia
+448,Pomenovaný argument sa nenašiel
+449,Argument nie je voliteľný
+450,Nesprávny počet argumentov alebo nesprávne priradenie vlastností
+451,Objekt nie je kolekciou
+453,Zadaná funkcia knižnice DLL sa nedá nájsť
+458,Premenná používa typ automatizácie, ktorý nie je podporovaný jazykom JavaScript
+462,Vzdialený server neexistuje alebo nie je k dispozícii
+501,Premennej sa nedá priradiť hodnota
+502,Spracovanie objektu skriptom nie je bezpečné
+503,Inicializácia objektu nie je bezpečná
+504,Vytvorenie objektu nie je bezpečné
+507,Vyskytla sa výnimka
+1001,Nedostatok pamäte
+1002,Syntaktická chyba
+1003,Očakáva sa ':'
+1004,Očakáva sa ';'
+1005,Očakáva sa '('
+1006,Očakáva sa ')'
+1007,Očakáva sa ']'
+1008,Očakáva sa '{'
+1009,Očakáva sa '}'
+1010,Očakáva sa identifikátor
+1011,Očakáva sa '='
+1012,Očakáva sa '/'
+1013,Neplatné číslo
+1014,Neplatný znak
+1015,Neukončená reťazcová konštanta
+1016,Neukončený komentár
+1018,Príkaz 'return' mimo funkcie
+1019,Príkaz 'break' nemožno použiť mimo cyklu
+1020,Príkaz 'continue' nemožno použiť mimo cyklu
+1023,Očakáva sa hexadecimálna číslica
+1024,Očakáva sa 'while'
+1025,Návestie sa predefinovalo
+1026,Menovka sa nedá nájsť
+1027,'default' sa môže v príkaze 'switch' nachádzať len raz
+1028,Očakáva sa identifikátor, reťazec alebo číslo
+1029,Očakáva sa '@end'
+1030,Podmienená kompilácia je vypnutá
+1031,Očakáva sa konštanta
+1032,Očakáva sa '@'
+1033,Očakáva sa 'catch'
+1034,Očakáva sa 'var'
+1035,Za príkazom throw musí na tom istom zdrojovom riadku nasledovať výraz
+1037,Prehlásenia with nie sú v prísnom režime povolené
+1038,Duplikáty názvov formálnych parametrov nie sú povolené v prísnom režime
+1039,Osmičkové číselné explicitné hodnoty premennej a znaky ukončenia nie sú v prísnom režime povolené
+1041,Neplatné použitie výrazu eval v prísnom režime
+1042,Neplatné použitie argumentov v prísnom režime
+1045,Volanie funkcie odstránenia pre výraz v prísnom režime nie je povolené
+1046,Viac definícií vlastnosti nie je v prísnom režime povolených
+1047,Deklarácie funkcií nemôžu byť v prísnom režime vnorené v príkaze alebo bloku. Môžu sa zobraziť len na najvyššej úrovni alebo priamo v tele funkcie.
+1048,Používanie kľúčového slova ako identifikátora je neplatné.
+1049,Používanie budúceho vyhradeného slova ako identifikátora je neplatné.
+1050,Používanie budúceho vyhradeného slova ako identifikátora je neplatné. Názov identifikátora je vyhradený v prísnom režime.
+1051,Funkcie metódy Setter musia mať jeden argument
+4096,JavaScript – chyba kompilácie
+4097,JavaScript – chyba počas práce
+4098,Neznáma chyba počas práce
+5000,Nemožno priradiť hodnotu premennej 'this'
+5001,Očakáva sa číslo
+5002,Očakáva sa funkcia
+5003,K výsledku funkcie nemožno priradiť hodnotu
+5004,Objekt sa nedá indexovať
+5005,Očakáva sa reťazec
+5006,Očakáva sa objekt typu dátum
+5007,Očakáva sa objekt
+5008,Neplatná ľavá strana v priradení
+5009,Nedefinovaný identifikátor
+5010,Očakáva sa objekt typu boolean
+5011,Nemožno vykonať kód z uvoľneného skriptu
+5012,Očakáva sa člen objektu
+5013,Očakáva sa objekt VBArray
+5014,Očakáva sa objekt jazyka JavaScript
+5015,Očakáva sa objekt Enumerator
+5016,Očakáva sa objekt typu Regular Expression
+5017,Syntaktická chyba v regulárnom výraze
+5018,Neočakávaný kvantifikátor
+5019,Očakáva sa ']' v regulárnom výraze
+5020,Očakáva sa ')' v regulárnom výraze
+5021,Neplatný rozsah znakovej sady
+5022,Výnimka bola vygenerovaná, ale nebola zachytená
+5023,Funkcia neobsahuje platný objekt prototypu
+5024,Kódované URI obsahuje neplatný znak
+5025,Dekódované URI nemá platné kódovanie
+5026,Počet desatinných miest je mimo rozsahu
+5027,Presnosť je mimo rozsahu
+5028,Očakáva sa objekt typu Array alebo argumenty
+5029,Dĺžka poľa musí byť konečné celé kladné číslo
+5030,Dĺžke poľa musí byť priradené konečné celé kladné číslo
+5031,Očakáva sa objekt typu Array
+5034,Zacyklený odkaz v argumente hodnoty nie je podporovaný
+5035,Neplatný argument náhrady
+5038,Zoznam argumentov je príliš dlhý a nedá sa použiť
+5039,Opakovaná deklarácia vlastnosti const
+5041,Člen objektu sa nedá konfigurovať
+5042,Nedefinovaná premenná v prísnom režime
+5043,Prístup k vlastnosti caller objektu funkcie alebo argumentov nie je v prísnom režime povolený.
+5044,Prístup k vlastnosti callee objektu argumentov nie je v prísnom režime povolený.
+5045,Priradenie k vlastnostiam určeným iba na čítanie nie je v prísnom režime povolené.
+5046,Nedá sa vytvoriť vlastnosť pre nerozšíriteľný objekt
+5047,Očakáva sa objekt
+5048,Očakáva sa objekt
+5049,Očakáva sa objekt
+5050,Očakáva sa objekt
+5051,Očakáva sa funkcia
+5052,Očakáva sa funkcia
+5053,Vlastnosť nemôže mať definované pristupovače zároveň s hodnotou
+5054,Parameter 'this' má hodnotu null alebo nie je definovaný
+5055,Očakáva sa objekt
+5056,Očakáva sa funkcia
+5057,Očakáva sa reťazec
+5058,Očakáva sa objekt typu boolean
+5059,Očakávaný dátum
+5060,Očakáva sa číslo
+5061,Očakáva sa objekt VBArray
+5062,Očakáva sa objekt jazyka JavaScript
+5063,Očakáva sa objekt Enumerator
+5064,Očakáva sa objekt RegExp
+5065,Neplatný argument funkcie
+5066,Očakáva sa objekt
+5067,Očakáva sa objekt jazyka JavaScript
+5068,Očakáva sa funkcia
+5069,Očakáva sa objekt VBArray
+5070,Očakáva sa objekt
+5071,Očakáva sa objekt
+5072,Neplatná vlastnosť length
+5073,Očakáva sa pole alebo objekt argumentov
+5074,Neplatný operand
+5075,Neplatný operand
+5076,Neplatný popisovač vlastnosti
+5077,Vlastnosť sa nepodarilo definovať: objekt nie je rozšíriteľný
+5078,Nie je možné predefinovať nekonfigurovateľnú vlastnosť
+5079,Nie je možné upraviť nezapisovateľnú vlastnosť
+5080,Nie je možné upraviť vlastnosť: vlastnosť length nie je zapisovateľná
+5081,Vlastnosť nie je možné definovať
+5082,Argument konštruktora zadaného poľa je neplatný.
+5083,„toto“ nie je objekt zadaného poľa.
+5084,Neplatný posun alebo neplatná dĺžka pri vytváraní zadaného poľa
+5085,Neplatná počiatočná alebo koncová hodnota v metóde podpolí zadaných polí
+5086,Neplatný zdroj v množine zadaných polí
+5087,„toto“ nie je objekt knižnice DataView
+5088,Neplatné argumenty v knižnici DataView
+5089,Prístup k operácii knižnice DataView prekročil zadanú dĺžku medzipamäte.
+5090,Neplatné argumenty v knižnici DataView
+5091,neplatný podpis funkcie
+5092,neplatný podpis vlastnosti
+5093,neplatný typ vstupného parametra
+5094,neplatný výstupný parameter
+5095,Prístup k vlastnosti arguments funkcie nie je v prísnom režime povolený.
+5096,Očakáva sa kontrolovateľný objekt.
+5097,Argument sa nepodarilo skonvertovať na typ char
+5098,Argument sa nepodarilo skonvertovať na typ GUID
+5099,Očakáva sa parameter IInspectable
+5100,Objekt sa nepodarilo skonvertovať na štruktúru: objektu chýba očakávaná vlastnosť
+5101,Neznámy typ
+5102,Funkcia bola vyvolaná pomocou príliš malého množstva argumentov
+5103,Typ nemožno zostaviť
+5104,Hodnotu sa nepodarilo skonvertovať na parameter PropertyValue: typ nie je podporovaný parametrom PropertyValue
+5105,Hodnotu sa nepodarilo skonvertovať na parameter IInspectable: typ nie je podporovaný parametrom IInspectable
+5106,Dátum sa nepodarilo skonvertovať na parameter Windows.Foundation.DateTime: hodnota mimo platného rozsahu
+5107,Hodnotu sa nepodarilo skonvertovať na parameter Windows.Foundation.TimeSpan: hodnota mimo platného rozsahu
+5108,Neplatný prístup k už uvoľnenému kontrolovateľnému objektu
+5109,Nie je možné uvoľniť už uvoľnený kontrolovateľný objekt
+5110,„toto“ nie je očakávaného typu
+5111,Bola zadaná nepovolená dĺžka a veľkosť poľa
+5112,Pri získavaní informácií o metaúdajoch sa vyskytla neočakávaná chyba
+5200,Stav je „chyba“, ale metóda getResults nevrátila chybu
+5201,Do dokončeného obslužného programu sa odovzdal chýbajúci alebo neplatný parameter stavu
+5202,Do dokončeného obslužného programu sa odovzdal chýbajúci alebo neplatný parameter odosielateľa
+6000,Nekonečno
+6001,-Nekonečno
+10438,Objekt nepodporuje vlastnosť alebo metódu %s
+10449,Argument pre funkciu %s nie je voliteľný
+15001,%s nie je číslo
+15002,%s nie je funkcia
+15004,%s nie je indexovateľný objekt
+15005,%s nie je reťazec
+15006,%s nie je objekt typu dátum
+15007,%s má hodnotu null alebo nie je objekt
+15008,Nemožno priradiť hodnotu k objektu %s
+15009,Objekt %s nie je definovaný
+15010,%s nie je objekt typu boolean
+15012,%s sa nedá odstrániť
+15013,%s nie je objekt typu VBArray
+15014,%s nie je objekt jazyka JavaScript
+15015,%s nie je objekt vymenovaného typu
+15016,%s nie je objekt typu výraz
+15028,%s nie je objekt typu Array ani argument
+15031,%s nie je objekt typu Array
+15036,Atribút %s na popisovači vlastnosti tohto objektu nie je možné nastaviť na hodnotu pravda
+15037,Atribút %s na popisovači vlastnosti tohto objektu nie je možné nastaviť na hodnotu nepravda
+15039,Opakovaná deklarácia konštanty %s
+15041,Volanie funkcie odstránenia pre funkciu %s nie je v prísnom režime povolené.
+15047,Nemožno nastaviť vlastnosť %s nedefinovaného odkazu alebo odkazu s hodnotou null
+15048,Nemožno získať vlastnosť %s nedefinovaného odkazu alebo odkazu s hodnotou null
+15049,Nemožno odstrániť vlastnosť %s nedefinovaného odkazu alebo odkazu s hodnotou null
+15050,Nedá sa pristupovať k vlastnosti %s: typ VarDate nepodporuje vlastnosti definované používateľom
+15051,Hodnota vlastnosti %s nie je objektom typu Funkcia
+15052,Hodnota vlastnosti %s je null alebo nie je definovaná, a nie je objektom typu Funkcia
+15054,%s: parameter 'this' má hodnotu null alebo nie je definovaný
+15055,%s: parameter 'this' nie je objektom
+15056,%s: parameter 'this' nie je objektom typu Funkcia
+15057,%s: parameter 'this' nie je objektom typu Reťazec
+15058,%s: parameter 'this' nie je booleovským objektom
+15059,%s: parameter 'this' nie je objektom typu Dátum
+15060,%s: parameter 'this' nie je nie je objektom typu Číslo
+15061,%s: parameter 'this' nie je objektom VBArray
+15062,%s: parameter 'this' nie je objektom jazyka JavaScript
+15063,%s: parameter 'this' nie je objektom vymenovaného typu
+15064,%s: parameter 'this' nie je nie je objektom RegExp
+15065,%s: neplatný argument
+15066,%s: argument nie je objektom
+15067,%s: argument nie je objektom jazyka JavaScript
+15068,%s: argument nie je objektom typu Funkcia
+15069,%s: argument nie je objektom VBArray
+15070,%s: argument má hodnotu null alebo nie je definovaný
+15071,%s: argument nie je objektom a nemá hodnotu null
+15072,%s: argument nemá platnú vlastnosť length
+15073,%s: očakáva sa pole alebo objekt argumentov
+15074,Neplatný operand pre parameter %s: očakáva sa objekt
+15075,Neplatný operand pre parameter %s: očakáva sa funkcia
+15076,Neplatný popisovač vlastnosti %s
+15077,Nie je možné definovať vlastnosť %s: objekt nie je rozšíriteľný
+15078,Nie je možné predefinovať nekonfigurovateľnú vlastnosť %s
+15079,Nie je možné upraviť nezapisovateľnú vlastnosť %s
+15080,Nie je možné upraviť vlastnosť %s: vlastnosť length nie je zapisovateľná
+15081,Nie je možné definovať vlastnosť %s
+15088,Nie je zadaný požadovaný argument %s v metóde knižnice DataView.
+15090,Argument konštruktora knižnice DataView %s je neplatný.
+15091,Podpis funkcie %s je neplatný a funkciu nemožno volať.
+15092,Podpis vlastnosti %s je neplatný a vlastnosť je neprístupná.
+15093,Trieda modulu runtime %s, ktorej predvoleným rozhraním je Windows.Foundation.IPropertyValue, nie je podporovaná ako typ vstupného parametra.
+15094,Objekt s rozhraním Windows.Foundation.IPropertyValue a názvom triedy modulu runtime %s nie je podporovaný ako výstupný parameter.
+15096,%s: „toto“ nie je kontrolovateľný objekt
+15097,%s: argument sa nepodarilo skonvertovať na typ char
+15098,%s: argument sa nepodarilo skonvertovať na typ GUID
+15099,%s: vrátenú hodnotu sa nepodarilo skonvertovať na parameter IInspectable
+15100,Objekt sa nepodarilo skonvertovať na štruktúru: objektu chýba očakávaná vlastnosť %s
+15101,Typ %s sa nenašiel
+15102,%s: funkcia vyvolaná pomocou príliš malého množstva argumentov
+15103,%s: typ nemožno zostaviť
+15104,Hodnotu sa nepodarilo sa skonvertovať na parameter PropertyValue: %s – nepodporované parametrom PropertyValue
+15105,Hodnotu sa nepodarilo sa skonvertovať na parameter IInspectable: %s – nepodporované parametrom IInspectable
+15108,%s: Kontrolovateľný objekt „toto“ je uvoľnený a nemožno k nemu získať prístup
+15110,„toto“ nie je očakávaného typu: %s
+15112,%s: pri získavaní informácií o metaúdajoch sa vyskytla neočakávaná chyba
+32812,Zadaný dátum nie je dostupný v kalendári aktuálneho národného prostredia
diff --git a/src/sentry/data/error-locale/sl-SI.txt b/src/sentry/data/error-locale/sl-SI.txt
new file mode 100644
index 00000000000000..c09dcb7a67bd59
--- /dev/null
+++ b/src/sentry/data/error-locale/sl-SI.txt
@@ -0,0 +1,289 @@
+5,Neveljaven argument ali klic procedure
+6,Prekoračitev obsega
+7,Zmanjkalo je pomnilnika
+9,Indeks je zunaj obsega
+10,To polje je nespremenljivo ali pa je začasno zaklenjeno
+11,Deljenje z nič
+13,Neujemanje tipov
+14,Zmanjkalo je prostora za nize
+17,Zahtevane operacije ni mogoče opraviti
+28,Zmanjkalo je prostora za sklad
+35,Podprogram ali funkcija ni definirana
+48,Napaka med nalaganjem DLL
+51,Notranja napaka
+52,Napačno ime datoteke ali število
+53,Datoteke ni mogoče najti
+54,Napačen datotečni način
+55,Datoteka je že odprta
+57,Vhodno/izhodna napaka naprave
+58,Datoteka že obstaja
+61,Disk je poln
+62,Vnos prek konca datoteke
+67,Preveč datotek
+68,Naprava ni na voljo
+70,Dostop zavrnjen
+71,Disk ni pripravljen
+74,Preimenovanje pogona ni mogoče
+75,Napaka pri dostopu do poti/datoteke
+76,Poti ni mogoče najti
+91,Spremenljivka predmeta ali bloka »With« ne obstaja
+92,Zanka »For« ni inicializirana
+94,Neveljavna uporaba izraza »Null«
+322,Potrebne začasne datoteke ni mogoče ustvariti
+424,Zahtevan je predmet
+429,Avtomatizacijski strežnik ne more ustvariti predmeta
+430,Razred ne podpira avtomatizacije
+432,Med izvajanjem avtomatizacije ni bilo mogoče najti imena datoteke ali razreda
+438,Predmet ne podpira te lastnosti ali metode
+440,Napaka avtomatizacije
+445,Predmet ne podpira tega dejanja
+446,Predmet ne podpira imenovanih argumentov
+447,Predmet ne podpira trenutnih področnih nastavitev
+448,Imenovanega argumenta ni bilo mogoče najti
+449,Argument je obvezen
+450,Napačno število argumentov ali neveljavna dodelitev lastnosti
+451,Predmet ni zbirka
+453,Navedene funkcije iz DLL-a ni bilo mogoče najti
+458,Spremenljivka uporablja vrsto avtomatizacije, ki je JScript ne podpira
+462,Oddaljeni strežnik ne obstaja ali pa ni na voljo
+501,Ni mogoče prirediti spremenljivki
+502,Predmeta ni varno uporabljati v skriptih
+503,Predmeta ni varno inicializirati
+504,Predmeta ni varno ustvariti
+507,Prišlo je do izjemne napake
+1001,Zmanjkalo je pomnilnika
+1002,Napaka v sintaksi
+1003,Pričakovan znak ':'
+1004,Pričakovan znak ';'
+1005,Pričakovan znak '('
+1006,Pričakovan zank ')'
+1007,Pričakovan znak ']'
+1008,Pričakovan znak '{'
+1009,Pričakovan znak '}'
+1010,Pričakovan identifikator
+1011,Pričakovan znak '='
+1012,Pričakovan znak '/'
+1013,Neveljavno število
+1014,Neveljaven znak
+1015,Nedokončana nizovna konstanta
+1016,Nedokončan komentar
+1018,Stavek 'return' zunaj funkcije
+1019,'break' ne more biti zunaj zanke
+1020,'continue' ne more biti zunaj zanke
+1023,Pričakovana šestnajstiška števka
+1024,Pričakovan niz 'while'
+1025,Oznaka predefinirana
+1026,Oznake ni bilo mogoče najti
+1027,'default' se lahko v stavku 'switch' pojavi le enkrat
+1028,Pričakovana je oznaka, niz ali številka
+1029,Pričakovan niz '@end'
+1030,Pogojno prevajanje je izklopljeno
+1031,Pričakovana konstanta
+1032,Pričakovan znak '@'
+1033,Pričakovan niz 'catch'
+1034,Pričakovan niz 'var'
+1035,Izjavi »throw« mora slediti izraz v isti izvorni vrstici
+1037,Izjave »with« niso dovoljene v strogem načinu
+1038,Podvojena uradna imena parametrov v strogem načinu niso dovoljena
+1039,Osmiški številčni nizi in ubežni znaki v strogem načinu niso dovoljeni
+1041,Neveljavna uporaba izraza »eval« v strogem načinu
+1042,Neveljavna uporaba argumentov v strogem načinu
+1045,Priklic brisanja za izraz v strogem načinu ni dovoljen
+1046,Več definicij lastnosti v strogem načinu ni dovoljenih
+1047,V strogem načinu deklaracij funkcije ni mogoče ugnezditi znotraj izjave ali bloka. Prikažejo se lahko le na najvišji ravni ali neposredno v telesu funkcije.
+1048,Uporaba ključne besede za identifikator je neveljavna
+1049,Uporaba prihodnje rezervirane besede za identifikator je neveljavna
+1050,Uporaba prihodnje rezervirane besede za identifikator je neveljavna. Ime identifikatorja je rezervirano v strogem načinu.
+1051,Funkcije nastavljalnika »setter« morajo imeti en argument
+4096,Napaka pri prevajanju skripta JavaScript
+4097,Napaka pri izvajanju skripta JavaScript
+4098,Neznana napaka pri izvajanju
+5000,Ni mogoče prirediti 'this'
+5001,Pričakovana številka
+5002,Pričakovana funkcija
+5003,Ni mogoče prirediti rezultatu funkcije
+5004,Predmeta ni mogoče indeksirati
+5005,Pričakovan niz
+5006,Pričakovan je datumski predmet
+5007,Pričakovan predmet
+5008,Neveljavna leva stran v dodelitvi
+5009,Nedefiniran identifikator
+5010,Pričakovan je »Boolean«
+5011,Kode iz sproščenega skripta ni mogoče izvršiti
+5012,Pričakovan član predmeta
+5013,Pričakovan VBArray
+5014,Predmet JavaScript je pričakovan
+5015,Pričakovan enumeracijski predmet
+5016,Pričakovan predmet regularni izraz
+5017,Napaka v sintaksi regularnega izraza
+5018,Nepričakovani kvantifikator
+5019,V regularnem izrazu je pričakovan znak ']'
+5020,V regularnem izrazu je pričakovan znak ')'
+5021,Neveljaven obseg v naboru znakov
+5022,Prišlo je do izjeme, ki ni bila prestrežena
+5023,Funkcija nima veljavnega prototipnega predmeta
+5024,URI, ki ga nameravate kodirati, vsebuje neveljaven znak
+5025,URI, ki ga nameravate dekodirati, ni pravilno kodiran
+5026,Število mest v števcu oz. v imenovalcu je zunaj obsega
+5027,Natančnost je zunaj obsega
+5028,Pričakovan je predmet polja ali argumenotv
+5029,Dolžina polja mora biti končno pozitivno celo število
+5030,Dolžini polja mora biti prirejeno končno pozitivno celo število
+5031,Pričakovan je predmet Array
+5034,Krožno sklicevanje v argumentu vrednosti ni podprto
+5035,Neveljaven argument nadomestnega elementa
+5038,Seznam argumentov je prevelik za uporabo
+5039,Vnovična deklaracija lastnosti »const«
+5041,Člana predmeta ni mogoče nastaviti
+5042,Spremenljivka v strogem načinu ni določena
+5043,Dostop do lastnosti »caller« funkcijskega predmeta ali predmeta argumentov v strogem načinu ni dovoljen
+5044,Dostop do lastnosti »callee« predmeta argumentov v strogem načinu ni dovoljen
+5045,Dodelitev za lastnosti samo za branje v strogem načinu ni dovoljena
+5046,Ni mogoče ustvariti lastnosti za nerazširljiv predmet
+5047,Pričakovan predmet
+5048,Pričakovan predmet
+5049,Pričakovan predmet
+5050,Pričakovan predmet
+5051,Pričakovana funkcija
+5052,Pričakovana funkcija
+5053,Lastnost ne more imeti dostopnikov in vrednosti
+5054,'this' je nič ali nedoločeno
+5055,Pričakovan predmet
+5056,Pričakovana funkcija
+5057,Pričakovan niz
+5058,Pričakovan logični izraz
+5059,Pričakovani datum
+5060,Pričakovana številka
+5061,Pričakovan VBArray
+5062,Predmet JavaScript je pričakovan
+5063,Pričakovan enumeracijski predmet
+5064,Predmet RegExp je pričakovan
+5065,Neveljaven argument funkcije
+5066,Pričakovan predmet
+5067,Predmet JavaScript je pričakovan
+5068,Pričakovana funkcija
+5069,Pričakovan VBArray
+5070,Pričakovan predmet
+5071,Pričakovan predmet
+5072,Neveljavna lastnost »length«
+5073,Pričakovan je predmet polja ali argumentov
+5074,Neveljaven operand
+5075,Neveljaven operand
+5076,Neveljaven deskriptor lastnosti
+5077,Lastnosti ni mogoče določiti: predmeta ni mogoče razširiti
+5078,Lastnosti, ki je ni mogoče konfigurirati, ni mogoče znova določiti
+5079,Nezapisljive lastnosti ni mogoče spremeniti
+5080,Lastnosti ni mogoče spremeniti: »length« ni zapisljiva
+5081,Lastnosti ni mogoče določiti
+5082,Argument konstruktorja tipizirane matrike ni veljaven
+5083,»this« ni tipiziran predmet matrike
+5084,Neveljaven odmik/dolžina pri ustvarjanju tipizirane matrike
+5085,Neveljavna začetna/končna vrednost v metodi »subarray « tipizirane matrike
+5086,Neveljaven vir v tipizirani množici matrik
+5087,»this« ni predmet »DataView«
+5088,Neveljavni argumenti v predmetu »DataView«
+5089,Operacija predmeta »DataView« presega določeno dolžino medpomnilnika
+5090,Neveljavni argumenti v pogledu podatkov
+5091,neveljaven podpis funkcije
+5092,neveljaven podpis lastnosti
+5093,neveljavna vrsta vhodnega parametra
+5094,neveljaven izhodni parameter
+5095,V strogem načinu dostop do lastnosti »argumentov« funkcije ni dovoljen
+5096,Pričakovan je predmet, ki ga je mogoče pregledati
+5097,Argumenta ni bilo mogoče pretvoriti v vrsto »char«
+5098,Argumenta ni bilo mogoče pretvoriti v vrsto »GUID«
+5099,Pričakovana je vrsta »IInspectable«
+5100,Predmeta ni mogoče pretvoriti v »struct«: v predmetu manjka pričakovana lastnosti
+5101,Neznana vrsta
+5102,Funkcija je poklicana s premalo argumenti
+5103,Vrste ni mogoče graditi
+5104,Vrednosti ni mogoče pretvoriti v »PropertyValue«: »PropertyValue« ne podpira te vrste
+5105,Vrednosti ni mogoče pretvoriti v »IInspectable«: »IInspectable« ne podpira te vrste
+5106,Datuma ni mogoče pretvoriti v »Windows.Foundation.DateTime«: vrednost je izven veljavnega obsega
+5107,Vrednosti ni mogoče pretvoriti v »Windows.Foundation.TimeSpan«: vrednost je izven veljavnega obsega
+5108,Neveljaven dostop do že objavljenega predmeta za pregled
+5109,Ni mogoče objaviti že objavljenega predmeta za pregled
+5110,»this« ni pričakovana vrsta
+5111,Za polje sta določeni neveljavna dolžina in velikost
+5112,Pri pridobivanju informacij o metapodatkih je prišlo do nepričakovane napake
+5200,Stanje je »error«, vendar getResults ni vrnil napake
+5201,Manjkajoč ali neveljaven parameter stanja je prešel na dokončano rutino za obravnavo
+5202,Manjkajoč ali neveljaven parameter pošiljatelja je prešel na dokončano rutino za obravnavo
+6000,Neskončno
+6001,-Neskončno
+10438,Predmet ne podpira lastnosti ali metode »%s«
+10449,Argument funkcije »%s« ni izbiren
+15001,»%s« ni številka
+15002,»%s« ni funkcija
+15004,»%s« je predmet, ki ga ni mogoče indeksirati
+15005,»%s« ni niz
+15006,»%s« ni datumski predmet
+15007,»%s« ima vrednost »null« ali pa ni predmet
+15008,Ni mogoče dodeliti »%s«
+15009,»%s« ni določen
+15010,»%s« ni logična vrednost
+15012,»%s« ni mogoče izbrisati
+15013,»%s« ni predmet VBArray
+15014,»%s« ni predmet jezika JavaScript
+15015,»%s« ni predmet popisovalnika
+15016,»%s« ni predmet regularni izraz
+15028,%s ni predmet polja ali argumentov
+15031,%s ni predmet polja
+15036,Atributa »%s« v deskriptorju lastnosti tega predmeta ni mogoče nastaviti na vrednost »true«
+15037,Atributa »%s« v deskriptorju lastnosti tega predmeta ni mogoče nastaviti na vrednost »false«
+15039,Vnovična deklaracija konstante »%s«
+15041,Priklic brisanja za »%s« ni dovoljen v strogem načinu
+15047,Lastnosti »%s« nedoločenega ali ničnega sklica ni mogoče nastaviti
+15048,Lastnosti »%s« nedoločenega ali ničnega sklica ni mogoče pridobiti
+15049,Lastnosti »%s« nedoločenega ali ničnega sklica ni mogoče izbrisati
+15050,Dostopa do lastnosti »%s« ni mogoč: vrsta »VarDate« ne podpira uporabniško določenih lastnosti
+15051,Vrednost lastnosti »%s« ni predmet funkcije
+15052,Vrednost lastnosti »%s« je »null« ali ni določena in ni predmet funkcije
+15054,%s: 'this' je »null« ali ni določeno
+15055,%s: 'this' ni predmet
+15056,%s: 'this' ni predmet funkcije
+15057,%s: 'this' ni predmet niza
+15058,%s: 'this' ni logični predmet
+15059,%s: 'this' ni datumski predmet
+15060,%s: 'this' ni številski predmet
+15061,%s: 'this' ni predmet VBArray
+15062,%s: 'this' ni predmet jezika JavaScript
+15063,%s: 'this' ni predmet popisovalnika
+15064,%s: 'this' ni predmet regularni izraz
+15065,%s: neveljaven argument
+15066,%s: argument ni predmet
+15067,%s: argument is ni predmet jezika JavaScript
+15068,%s: argument ni predmet funkcije
+15069,%s: argument ni predmet VBArray
+15070,%s: argument je »null« ali ni določen
+15071,%s: argument ni predmet in ni »null«
+15072,%s: argument nima veljavne lastnosti »length«
+15073,%s: pričakovan je predmet polja ali argumentov
+15074,Neveljavni operand za »%s«: pričakovan je predmet
+15075,Neveljaven operand za »%s«: pričakovana je funkcija
+15076,Neveljaven deskriptor lastnosti »%s«
+15077,Lastnosti »%s« ni mogoče določiti: predmeta ni mogoče razširiti
+15078,Lastnosti »%s«, ki je ni mogoče konfigurirati, ni mogoče znova določiti
+15079,Nezapisljive lastnosti »%s« ni mogoče spremeniti
+15080,Lastnosti »%s« ni mogoče spremeniti: »length« ni zapisljiva
+15081,Lastnosti »%s« ni mogoče določiti
+15088,Obvezni argument %s metode predmeta »DataView« ni določen
+15090,Argument %s konstruktorja predmeta »DataView« ni veljaven
+15091,Podpis funkcije »%s« ni veljaven, zato je ni mogoče priklicati
+15092,Podpis lastnosti »%s« ni veljaven, zato dostop do nje ni mogoč
+15093,Izvajalni razred %s, ki ima Windows.Foundation.IPropertyValue za privzeti vmesnik, ni podprt kot tip vhodnega parametra
+15094,Predmet z vmesnikom Windows.Foundation.IPropertyValue, ki ima ime izvajalnega razreda %s, ni podprt kot izhodni parameter
+15096,%s: »this« ni predmet, ki ga je mogoče pregledati
+15097,%s: argumenta ni bilo mogoče pretvoriti v vrsto »char«
+15098,%s: argumenta ni bilo mogoče pretvoriti v vrsto »GUID«
+15099,%s: povratne vrednosti ni bilo mogoče pretvoriti v »IInspectable«
+15100,Predmeta ni bilo mogoče pretvoriti v »struct«: predmet nima pričakovane lastnosti »%s«
+15101,Vrste »%s« ni mogoče najti
+15102,%s: funkcija je poklicana s premalo argumenti
+15103,%s: vrste ni mogoče graditi
+15104,Vrednosti ni mogoče pretvoriti v »PropertyValue«: »PropertyValue« ne podpira %s
+15105,Vrednosti ni mogoče pretvoriti v »IInspectable«: »IInspectable« ne podpira %s
+15108,%s: predmet za pregled »this« je objavljen in ni mogoče dostopati do njega
+15110,»this« ni pričakovana vrsta: %s
+15112,%s: Pri pridobivanju informacij o metapodatkih je prišlo do nepričakovane napake
+32812,Navedeni datum ni na voljo v koledarju trenutnih področnih nastavitev.
diff --git a/src/sentry/data/error-locale/sr-Latn-CS.txt b/src/sentry/data/error-locale/sr-Latn-CS.txt
new file mode 100644
index 00000000000000..6580983d9b4a88
--- /dev/null
+++ b/src/sentry/data/error-locale/sr-Latn-CS.txt
@@ -0,0 +1,289 @@
+5,Invalid procedure call or argument
+6,Overflow
+7,Out of memory
+9,Subscript out of range
+10,This array is fixed or temporarily locked
+11,Division by zero
+13,Type mismatch
+14,Out of string space
+17,Can't perform requested operation
+28,Out of stack space
+35,Sub or Function not defined
+48,Error in loading DLL
+51,Internal error
+52,Bad file name or number
+53,File not found
+54,Bad file mode
+55,File already open
+57,Device I/O error
+58,File already exists
+61,Disk full
+62,Input past end of file
+67,Too many files
+68,Device unavailable
+70,Permission denied
+71,Disk not ready
+74,Can't rename with different drive
+75,Path/File access error
+76,Path not found
+91,Object variable or With block variable not set
+92,For loop not initialized
+94,Invalid use of Null
+322,Can't create necessary temporary file
+424,Object required
+429,Automation server can't create object
+430,Class doesn't support Automation
+432,File name or class name not found during Automation operation
+438,Object doesn't support this property or method
+440,Automation error
+445,Object doesn't support this action
+446,Object doesn't support named arguments
+447,Object doesn't support current locale setting
+448,Named argument not found
+449,Argument not optional
+450,Wrong number of arguments or invalid property assignment
+451,Object not a collection
+453,Specified DLL function not found
+458,Variable uses an Automation type not supported in JavaScript
+462,The remote server machine does not exist or is unavailable
+501,Cannot assign to variable
+502,Object not safe for scripting
+503,Object not safe for initializing
+504,Object not safe for creating
+507,An exception occurred
+1001,Out of memory
+1002,Syntax error
+1003,Expected ':'
+1004,Expected ';'
+1005,Expected '('
+1006,Expected ')'
+1007,Expected ']'
+1008,Expected '{'
+1009,Expected '}'
+1010,Expected identifier
+1011,Expected '='
+1012,Expected '/'
+1013,Invalid number
+1014,Invalid character
+1015,Unterminated string constant
+1016,Unterminated comment
+1018,'return' statement outside of function
+1019,Can't have 'break' outside of loop
+1020,Can't have 'continue' outside of loop
+1023,Expected hexadecimal digit
+1024,Expected 'while'
+1025,Label redefined
+1026,Label not found
+1027,'default' can only appear once in a 'switch' statement
+1028,Expected identifier, string or number
+1029,Expected '@end'
+1030,Conditional compilation is turned off
+1031,Expected constant
+1032,Expected '@'
+1033,Expected 'catch'
+1034,Expected 'var'
+1035,'throw' must be followed by an expression on the same source line
+1037,'with' statements are not allowed in strict mode
+1038,Duplicate formal parameter names not allowed in strict mode
+1039,Octal numeric literals and escape characters not allowed in strict mode
+1041,Invalid usage of 'eval' in strict mode
+1042,Invalid usage of 'arguments' in strict mode
+1045,Calling delete on expression not allowed in strict mode
+1046,Multiple definitions of a property not allowed in strict mode
+1047,In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.
+1048,The use of a keyword for an identifier is invalid
+1049,The use of a future reserved word for an identifier is invalid
+1050,The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.
+1051,Setter functions must have one argument
+4096,JavaScript compilation error
+4097,JavaScript runtime error
+4098,Unknown runtime error
+5000,Cannot assign to 'this'
+5001,Number expected
+5002,Function expected
+5003,Cannot assign to a function result
+5004,Cannot index object
+5005,String expected
+5006,Date object expected
+5007,Object expected
+5008,Invalid left-hand side in assignment
+5009,Undefined identifier
+5010,Boolean expected
+5011,Can't execute code from a freed script
+5012,Object member expected
+5013,VBArray expected
+5014,JavaScript object expected
+5015,Enumerator object expected
+5016,Regular Expression object expected
+5017,Syntax error in regular expression
+5018,Unexpected quantifier
+5019,Expected ']' in regular expression
+5020,Expected ')' in regular expression
+5021,Invalid range in character set
+5022,Exception thrown and not caught
+5023,Function does not have a valid prototype object
+5024,The URI to be encoded contains an invalid character
+5025,The URI to be decoded is not a valid encoding
+5026,The number of fractional digits is out of range
+5027,The precision is out of range
+5028,Array or arguments object expected
+5029,Array length must be a finite positive integer
+5030,Array length must be assigned a finite positive number
+5031,Array object expected
+5034,Circular reference in value argument not supported
+5035,Invalid replacer argument
+5038,Argument list too large to apply
+5039,Redeclaration of const property
+5041,Object member not configurable
+5042,Variable undefined in strict mode
+5043,Accessing the 'caller' property of a function or arguments object is not allowed in strict mode
+5044,Accessing the 'callee' property of an arguments object is not allowed in strict mode
+5045,Assignment to read-only properties is not allowed in strict mode
+5046,Cannot create property for a non-extensible object
+5047,Object expected
+5048,Object expected
+5049,Object expected
+5050,Object expected
+5051,Function expected
+5052,Function expected
+5053,Property cannot have both accessors and a value
+5054,'this' is null or undefined
+5055,Object expected
+5056,Function expected
+5057,String expected
+5058,Boolean expected
+5059,Date expected
+5060,Number expected
+5061,VBArray expected
+5062,JavaScript object expected
+5063,Enumerator object expected
+5064,RegExp object expected
+5065,Invalid function argument
+5066,Object expected
+5067,JavaScript object expected
+5068,Function expected
+5069,VBArray expected
+5070,Object expected
+5071,Object expected
+5072,Invalid 'length' property
+5073,Array or arguments object expected
+5074,Invalid Operand
+5075,Invalid Operand
+5076,Invalid property descriptor
+5077,Cannot define property: object is not extensible
+5078,Cannot redefine non-configurable property
+5079,Cannot modify non-writable property
+5080,Cannot modify property: 'length' is not writable
+5081,Cannot define property
+5082,Typed array constructor argument is invalid
+5083,'this' is not a typed array object
+5084,Invalid offset/length when creating typed array
+5085,Invalid begin/end value in typed array subarray method
+5086,Invalid source in typed array set
+5087,'this' is not a DataView object
+5088,Invalid arguments in DataView
+5089,DataView operation access beyond specified buffer length
+5090,Invalid arguments in DataView
+5091,invalid function signature
+5092,invalid property signature
+5093,invalid input parameter type
+5094,invalid ouput parameter
+5095,Accessing the 'arguments' property of a function is not allowed in strict mode
+5096,Inspectable Object expected
+5097,Could not convert argument to type 'char'
+5098,Could not convert argument to type 'GUID'
+5099,IInspectable expected
+5100,Could not convert object to struct: object missing expected property
+5101,Unknown type
+5102,Function called with too few arguments
+5103,Type is not constructible
+5104,Could not convert value to PropertyValue: Type not supported by PropertyValue
+5105,Could not convert value to IInspectable: Type not supported by IInspectable
+5106,Could not convert Date to Windows.Foundation.DateTime: value outside of valid range
+5107,Could not convert value to Windows.Foundation.TimeSpan: value outside of valid range
+5108,Invalid access to already released Inspectable Object
+5109,Cannot release already released Inspectable Object
+5110,'this' is not of the expected type
+5111,Illegal length and size specified for the array
+5112,An unexpected failure occurred while trying to obtain metadata information
+5200,Status is 'error', but getResults did not return an error
+5201,Missing or invalid status parameter passed to completed handler
+5202,Missing or invalid sender parameter passed to completed handler
+6000,Infinity
+6001,-Infinity
+10438,Object doesn't support property or method '%s'
+10449,Argument to the function '%s' is not optional
+15001,'%s' is not a number
+15002,'%s' is not a function
+15004,'%s' is not an indexable object
+15005,'%s' is not a string
+15006,'%s' is not a date object
+15007,'%s' is null or not an object
+15008,Cannot assign to '%s'
+15009,'%s' is undefined
+15010,'%s' is not a boolean
+15012,Cannot delete '%s'
+15013,'%s' is not a VBArray
+15014,'%s' is not a JavaScript object
+15015,'%s' is not an enumerator object
+15016,'%s' is not a regular expression object
+15028,%s is not an Array or arguments object
+15031,%s is not an Array object
+15036,'%s' attribute on the property descriptor cannot be set to 'true' on this object
+15037,'%s' attribute on the property descriptor cannot be set to 'false' on this object
+15039,Redeclaration of const '%s'
+15041,Calling delete on '%s' is not allowed in strict mode
+15047,Unable to set property '%s' of undefined or null reference
+15048,Unable to get property '%s' of undefined or null reference
+15049,Unable to delete property '%s' of undefined or null reference
+15050,Unable to access property '%s': type 'VarDate' does not support user-defined properties
+15051,The value of the property '%s' is not a Function object
+15052,The value of the property '%s' is null or undefined, not a Function object
+15054,%s: 'this' is null or undefined
+15055,%s: 'this' is not an Object
+15056,%s: 'this' is not a Function object
+15057,%s: 'this' is not a String object
+15058,%s: 'this' is not a Boolean object
+15059,%s: 'this' is not a Date object
+15060,%s: 'this' is not a Number object
+15061,%s: 'this' is not a VBArray object
+15062,%s: 'this' is not a JavaScript object
+15063,%s: 'this' is not an Enumerator object
+15064,%s: 'this' is not a RegExp object
+15065,%s: invalid argument
+15066,%s: argument is not an Object
+15067,%s: argument is not a JavaScript object
+15068,%s: argument is not a Function object
+15069,%s: argument is not a VBArray object
+15070,%s: argument is null or undefined
+15071,%s: argument is not an Object and is not null
+15072,%s: argument does not have a valid 'length' property
+15073,%s: Array or arguments object expected
+15074,Invalid operand to '%s': Object expected
+15075,Invalid operand to '%s': Function expected
+15076,Invalid descriptor for property '%s'
+15077,Cannot define property '%s': object is not extensible
+15078,Cannot redefine non-configurable property '%s'
+15079,Cannot modify non-writable property '%s'
+15080,Cannot modify property '%s': 'length' is not writable
+15081,Cannot define property '%s'
+15088,Required argument %s in DataView method is not specified
+15090,DataView constructor argument %s is invalid
+15091,The function '%s' has an invalid signature and cannot be called
+15092,The property '%s' has an invalid signature and cannot be accessed
+15093,The runtimeclass %s that has Windows.Foundation.IPropertyValue as default interface is not supported as input parameter type
+15094,The object with interface Windows.Foundation.IPropertyValue that has runtimeclass name %s is not supported as out parameter
+15096,%s: 'this' is not an Inspectable Object
+15097,%s: could not convert argument to type 'char'
+15098,%s: could not convert argument to type 'GUID'
+15099,%s: could not convert return value to IInspectable
+15100,Could not convert object to struct: object missing expected property '%s'
+15101,Type '%s' not found
+15102,%s: function called with too few arguments
+15103,%s: type is not constructible
+15104,Could not convert value to PropertyValue: %s not supported by PropertyValue
+15105,Could not convert value to IInspectable: %s not supported by IInspectable
+15108,%s: The Inspectable object 'this' is released and cannot be accessed
+15110,'this' is not of expected type: %s
+15112,%s: an unexpected failure occurred while trying to obtain metadata information
+32812,The specified date is not available in the current locale's calendar
diff --git a/src/sentry/data/error-locale/sv-SE.txt b/src/sentry/data/error-locale/sv-SE.txt
new file mode 100644
index 00000000000000..8eb0d332a5dcb7
--- /dev/null
+++ b/src/sentry/data/error-locale/sv-SE.txt
@@ -0,0 +1,289 @@
+5,Ogiltigt proceduranrop eller argument.
+6,Spill
+7,Slut på minne.
+9,Felaktigt matrisindex
+10,Matrisen är fast eller tillfälligt låst.
+11,Division med noll
+13,Inkompatibla typer
+14,Slut på strängutrymme.
+17,Det går inte att utföra begärd åtgärd
+28,Slut på stackutrymme.
+35,Sub eller Function har inte definierats
+48,Det går inte att ladda DLL-filen
+51,Internt fel
+52,Felaktigt filnamn eller filnummer
+53,Det går inte att hitta filen.
+54,Felaktigt filåtkomstläge
+55,Filen är redan öppen
+57,Läs/skrivfel
+58,Filen finns redan.
+61,Slut på diskutrymme.
+62,Indata överskrider filslut
+67,För många öppna filer.
+68,Enheten är inte tillgänglig
+70,Åtkomst nekad.
+71,Disken är inte klar
+74,Det går inte att döpa om filen på en annan enhet
+75,Det går inte att hitta angiven fil eller sökväg.
+76,Det går inte att hitta angiven sökväg.
+91,Objektvariabel eller With-blockvariabel har inte angetts
+92,For-loop har inte initierats
+94,Ogiltig användning av Null
+322,Det går inte att skapa nödvändig temporär fil
+424,Objekt krävs.
+429,Automation-server kan inte skapa objekt.
+430,Klassen stöder inte Automation.
+432,Det går inte att hitta fil- eller klassnamn under Automation-åtgärden.
+438,Objektet stöder inte egenskapen eller metoden.
+440,Automation-fel
+445,Objektet kan inte hantera denna åtgärd.
+446,Namngivna argument stöds inte av objektet.
+447,Objektet stöder inte de aktuella landsinställningarna.
+448,Det namngivna argumentet finns inte.
+449,Argumentet är inte valfritt.
+450,Fel antal argument eller felaktig egenskapstilldelning.
+451,Objektet måste vara en mängd.
+453,Det går inte att hitta angiven DLL-funktion.
+458,Variabeln använder en Automation-typ som inte kan användas i JavaScript.
+462,Fjärrservern finns inte eller också är den otillgänglig
+501,Det går inte att tilldela en variabel.
+502,Objektet är inte säkert nog att använda i skript.
+503,Objektet är inte säkert nog att initiera.
+504,Objektet är inte säkert och kan inte skapas
+507,Ett undantag uppstod
+1001,Slut på minne.
+1002,Syntaxfel
+1003,':' förväntas
+1004,';' förväntas
+1005,'(' förväntas
+1006,')' förväntas
+1007,']' förväntas
+1008,'{' förväntas
+1009,'}' förväntas
+1010,Identifierare förväntas.
+1011,'=' förväntas
+1012,'/' förväntas
+1013,Ogiltigt tal
+1014,Ogiltigt tecken
+1015,Oavslutad strängkonstant
+1016,Oavslutad kommentar
+1018,'return'-instruktion får inte förekomma utanför funktion.
+1019,'break' får inte förekomma utanför loop.
+1020,'continue' får inte förekomma utanför loop.
+1023,Ett hexadecimalt tal förväntas.
+1024,Förväntade 'while'
+1025,Definiera om etikett.
+1026,Etiketten kunde inte hittas.
+1027,'default' kan bara förekomma en gång i en 'switch' -instruktion.
+1028,Identifierare, sträng eller tal förväntas
+1029,'@end' förväntas
+1030,Villkorlig kompilering har stängts av
+1031,Konstant förväntas.
+1032,'@' förväntas.
+1033,'catch' förväntas
+1034,'var' förväntas
+1035,throw måste följas av ett uttryck på samma källrad
+1037,with-instruktioner tillåts inte i strikt läge
+1038,Dubblettnamn på formella parametrar tillåts inte i strikt läge
+1039,Oktala numeriska litteraler och escape-tecken tillåts inte i strikt läge
+1041,Ogiltig användning av eval i strikt läge
+1042,Ogiltig användning av arguments i strikt läge
+1045,Anrop av delete i uttryck tillåts inte i strikt läge
+1046,Flera definitioner för en egenskap tillåts inte i strikt läge
+1047,I strikt läge kan inte function-deklarationer kapslas i ett villkor eller ett block. De kan endast visas vid toppnivån eller direkt i brödtext för function.
+1048,Användandet av ett nyckelord för en identifierare är ogiltigt
+1049,Användandet av ett reserverat ord för en identifierare är ogiltigt
+1050,Användandet av ett reserverat ord för en identifierare är ogiltigt. Namnet på identifieraren är reserverat i strikt läge.
+1051,Setter-funktioner måste ha ett argument
+4096,Kompileringsfel i JavaScript
+4097,Körningsfel i JavaScript
+4098,Okänt körningsfel
+5000,Det går inte att tilldela 'this'.
+5001,Ett tal förväntas.
+5002,En funktion förväntas.
+5003,Det går inte att tilldela ett funktionsresultat.
+5004,Det går inte att indexera objekt.
+5005,En sträng förväntas.
+5006,Ett datumobjekt förväntas.
+5007,Ett objekt förväntas.
+5008,Vänster sida i tilldelningen är ogiltig
+5009,Odefinierad identifierare.
+5010,Ett boolskt värde förväntas.
+5011,Det går inte att köra kod från detta skript
+5012,Objektmedlem förväntas
+5013,VBArray förväntas
+5014,Ett JavaScript-objekt förväntas
+5015,Uppräkningsobjekt förväntas
+5016,Ett reguljärt uttryck förväntas
+5017,Syntaxfel i reguljärt uttryck
+5018,Oväntad kvantifierare
+5019,Förväntade ']' i reguljärt uttryck
+5020,Förväntade ')' i reguljärt uttryck
+5021,Ogiltigt intervall i teckenuppsättning
+5022,Undantag thrown och inte caught
+5023,Funktionen saknar ett giltigt prototypobjekt
+5024,Den URI som ska kodas innehåller ett ogiltigt tecken
+5025,Den URI som ska avkodas är inte en giltig kodning
+5026,Antalet bråktal är utanför intervallet
+5027,Precisionen är utanför intervallet
+5028,Ett Array- eller arguments-objekt förväntas
+5029,Matrislängden måste vara ett ändligt positivt heltal
+5030,Ett ändligt positivt heltal måste anges för matrislängden
+5031,Array-objekt förväntas
+5034,Cirkelreferens i värdeargument stöds inte
+5035,Ogiltigt ersättningsargument
+5038,Argumentlistan är för stor
+5039,Omdeklaration av const-egenskap
+5041,Objektmedlem kan inte konfigureras
+5042,Odefinierad variabel i strikt läge
+5043,Åtkomst av caller-egenskapen för en funktion eller ett arguments-objekt tillåts inte i strikt läge
+5044,Åtkomst av callee-egenskapen för ett arguments-objekt tillåts inte i strikt läge
+5045,Tilldelning av skrivskyddade egenskaper tillåts inte i strikt läge
+5046,Det går inte att skapa en egenskap för ett objekt som inte är extensible
+5047,Ett objekt förväntas.
+5048,Ett objekt förväntas.
+5049,Ett objekt förväntas.
+5050,Ett objekt förväntas.
+5051,En funktion förväntas.
+5052,En funktion förväntas.
+5053,Egenskapen kan inte ha både accessorer och ett värde
+5054,'this' är null eller odefinierat
+5055,Ett objekt förväntas
+5056,En funktion förväntas.
+5057,En sträng förväntas.
+5058,Ett boolskt värde förväntas.
+5059,Ett datum förväntas
+5060,Ett tal förväntas.
+5061,VBArray förväntas
+5062,Ett JavaScript-objekt förväntas
+5063,Uppräkningsobjekt förväntas
+5064,RegExp-objekt förväntas
+5065,Ogiltigt funktionsargument
+5066,Ett objekt förväntas.
+5067,Ett JavaScript-objekt förväntas
+5068,En funktion förväntas.
+5069,VBArray förväntas
+5070,Ett objekt förväntas.
+5071,Ett objekt förväntas.
+5072,Ogiltig length-egenskap
+5073,Ett Array- eller arguments-objekt förväntas
+5074,Ogiltig operand
+5075,Ogiltig operand
+5076,Ogiltig egenskapsbeskrivning
+5077,Det går inte att definiera egenskapen: objektet är inte utökningsbart
+5078,Det går inte att definiera om den icke-konfigurerbara egenskapen
+5079,Det går inte att ändra den icke-skrivbara egenskapen
+5080,Det går inte att ändra egenskapen: length är inte skrivbart
+5081,Det går inte att definiera egenskapen
+5082,Det angivna matriskonstruktorargumentet är ogiltigt
+5083,this är inte ett angivet matrisobjekt
+5084,Ogiltig förskjutning/längd när angiven matris skapades
+5085,Ogiltigt början/slut i angiven matris/undermatris-metod
+5086,Ogiltig källa i angiven matrisuppsättning
+5087,this är inte ett DataView-objekt
+5088,Ogiltiga argument i DataView
+5089,DataView-operationen överstiger den angivna buffertlängden
+5090,Ogiltiga argument i DataView
+5091,ogiltig funktionssignatur
+5092,ogiltig egenskapssignatur
+5093,ogiltig inmatningsparametertyp
+5094,ogiltig utdataparameter
+5095,Åtkomst till egenskapen argument för en funktion är inte tillåten i strikt läge
+5096,Inspectable-objekt förväntades
+5097,Det gick inte att konvertera argumentet till typen char
+5098,Det gick inte att konvertera argumentet till typen GUID
+5099,IInspectable förväntades
+5100,Det gick inte att konvertera objektet till en struktur: objektet saknar en förväntad egenskap
+5101,Okänd typ
+5102,Funktionen anropades med för få argument
+5103,Typen är inte konstruerbar
+5104,Det gick inte att konvertera värdet till PropertyValue: typen stöds inte av PropertyValue
+5105,Det gick inte att konvertera värdet till IInspectable: typen stöds inte av IInspectable
+5106,Det gick inte att konvertera datumet till Windows.Foundation.DateTime: värdet ligger utanför det tillåtna intervallet
+5107,Det gick inte att konvertera värdet till Windows.Foundation.TimeSpan: värdet ligger utanför det tillåtna intervallet
+5108,Ogiltig åtkomst till redan frisläppt Inspectable-objekt
+5109,Det går inte att frisläppa ett redan frisläppt Inspectable-objekt
+5110,this är inte av den förväntade typen
+5111,Ogiltig längd och storlek har angetts för matrisen
+5112,Ett oväntat fel uppstod under ett försök att erhålla metadatainformation
+5200,Status är error, men inget fel returnerades av getResults
+5201,En status-parameter som saknas eller är ogiltig har skickats till den slutförda hanteraren
+5202,En sender-parameter som saknas eller är ogiltig har skickats till den slutförda hanteraren
+6000,Oändligt
+6001,-Oändligt
+10438,Egenskapen eller metoden %s stöds inte av objektet
+10449,Argumentet för funktionen %s är inte valfritt
+15001,%s är inte ett tal
+15002,%s är inte en funktion
+15004,%s är inte ett indexerbart objekt
+15005,%s är inte en sträng
+15006,%s är inte ett datumobjekt
+15007,%s har värdet null eller är inte ett objekt
+15008,Det går inte att tilldela till %s
+15009,%s har inte definierats
+15010,%s är inte ett booleskt värde
+15012,Det går inte att ta bort %s
+15013,%s är inte en VBArray
+15014,%s är inte ett JavaScript-objekt
+15015,%s är inte ett uppräkningsobjekt
+15016,%s är inte ett reguljärt uttryck
+15028,%s är inte ett Array- eller arguments-objekt
+15031,%s är inte ett Array-objekt
+15036,Attributet %s för egenskapsbeskrivningen får inte ha värdet true för det här objektet
+15037,Attributet %s för egenskapsbeskrivningen får inte ha värdet false för det här objektet
+15039,Omdeklaration av const %s
+15041,Det är inte tillåtet att anropa delete för %s i strikt läge
+15047,Det går inte att ange egenskapen %s för en referens som är odefinierad eller null
+15048,Det går inte att hämta egenskapen %s för en referens som är odefinierad eller null
+15049,Det går inte att ta bort egenskapen %s för en referens som är odefinierad eller null
+15050,Det går inte att komma åt egenskapen %s: typen VarDate stöder inte användardefinierade egenskaper
+15051,Värdet för egenskapen %s är inte ett Function-objekt
+15052,Värdet för egenskapen %s är null eller odefinierat, inte ett Function-objekt
+15054,%s: 'this' är null eller odefinierat
+15055,%s: 'this' är inte ett objekt
+15056,%s: 'this' är inte ett Function-objekt
+15057,%s: 'this' är inte ett String-objekt
+15058,%s: 'this' är inte ett Boolean-objekt
+15059,%s: 'this' är inte ett Date-objekt
+15060,%s: 'this' är inte ett Number-objekt
+15061,%s: 'this' är inte ett VBArray-objekt
+15062,%s: 'this' är inte ett JavaScript-objekt
+15063,%s: 'this' är inte ett Enumerator-objekt
+15064,%s: 'this' är inte ett RegExp-objekt
+15065,%s: ogiltigt argument
+15066,%s: argumentet är inte ett objekt
+15067,%s: argumentet är inte ett JavaScript-objekt
+15068,%s: argumentet är inte ett Function-objekt
+15069,%s: argumentet är inte ett VBArray-objekt
+15070,%s: argumentet är null eller odefinierat
+15071,%s: argumentet är inte ett objekt och är inte null
+15072,%s: argumentet har inte en giltig length-egenskap
+15073,%s: Array- eller arguments-objekt förväntas
+15074,Ogiltig operand för %s: Objekt förväntas
+15075,Ogiltig operand för %s: Funktion förväntas
+15076,Ogiltig beskrivning för egenskapen %s
+15077,Det går inte att definiera egenskapen %s: objektet är inte utökningsbart
+15078,Det går inte att definiera om den icke-konfigurerbara egenskapen %s
+15079,Det går inte att ändra den icke-skrivbara egenskapen %s
+15080,Det går inte att ändra egenskapen %s: length är inte skrivbart
+15081,Det går inte att definiera egenskapen %s
+15088,Det nödvändiga argumentet %s i DataView-metod har inte angetts
+15090,DataView-konstruktorargumentet %s är ogiltigt
+15091,Funktionen %s har en ogiltig signatur och kan inte anropas
+15092,Egenskapen %s har en ogiltig signatur och går inte att komma åt
+15093,Runtime-klass %s med Windows.Foundation.IPropertyValue som standardgränssnitt stöds inte som inmatningsparametertyp
+15094,Objektet med gränssnitt Windows.Foundation.IPropertyValue som har runtime-klassnamn %s stöds inte som utdataparameter
+15096,%s: this är inget Inspectable-objekt
+15097,%s: det gick inte att konvertera argumentet till typen char
+15098,%s: det gick inte att konvertera argumentet till typen GUID
+15099,%s: det gick inte att konvertera returvärdet till IInspectable
+15100,Det gick inte att konvertera objektet till en struktur: objektet saknar den förväntade egenskapen %s
+15101,Typen %s hittades inte
+15102,%s: funktionen anropades med för få argument
+15103,%s: typen är inte konstruerbar
+15104,Det gick inte att konvertera värdet till PropertyValue: %s stöds inte av PropertyValue
+15105,Det gick inte att konvertera värdet till IInspectable: %s stöds inte av IInspectable
+15108,%s: Inspectable-objektet this har frisläppts och går inte att komma åt
+15110,this är inte av den förväntade typen: %s
+15112,%s: Ett oväntat fel uppstod under ett försök att erhålla metadatainformation
+32812,Det angivna datumet är inte tillgängligt i kalendern för den aktuella platsen
diff --git a/src/sentry/data/error-locale/th-TH.txt b/src/sentry/data/error-locale/th-TH.txt
new file mode 100644
index 00000000000000..6580983d9b4a88
--- /dev/null
+++ b/src/sentry/data/error-locale/th-TH.txt
@@ -0,0 +1,289 @@
+5,Invalid procedure call or argument
+6,Overflow
+7,Out of memory
+9,Subscript out of range
+10,This array is fixed or temporarily locked
+11,Division by zero
+13,Type mismatch
+14,Out of string space
+17,Can't perform requested operation
+28,Out of stack space
+35,Sub or Function not defined
+48,Error in loading DLL
+51,Internal error
+52,Bad file name or number
+53,File not found
+54,Bad file mode
+55,File already open
+57,Device I/O error
+58,File already exists
+61,Disk full
+62,Input past end of file
+67,Too many files
+68,Device unavailable
+70,Permission denied
+71,Disk not ready
+74,Can't rename with different drive
+75,Path/File access error
+76,Path not found
+91,Object variable or With block variable not set
+92,For loop not initialized
+94,Invalid use of Null
+322,Can't create necessary temporary file
+424,Object required
+429,Automation server can't create object
+430,Class doesn't support Automation
+432,File name or class name not found during Automation operation
+438,Object doesn't support this property or method
+440,Automation error
+445,Object doesn't support this action
+446,Object doesn't support named arguments
+447,Object doesn't support current locale setting
+448,Named argument not found
+449,Argument not optional
+450,Wrong number of arguments or invalid property assignment
+451,Object not a collection
+453,Specified DLL function not found
+458,Variable uses an Automation type not supported in JavaScript
+462,The remote server machine does not exist or is unavailable
+501,Cannot assign to variable
+502,Object not safe for scripting
+503,Object not safe for initializing
+504,Object not safe for creating
+507,An exception occurred
+1001,Out of memory
+1002,Syntax error
+1003,Expected ':'
+1004,Expected ';'
+1005,Expected '('
+1006,Expected ')'
+1007,Expected ']'
+1008,Expected '{'
+1009,Expected '}'
+1010,Expected identifier
+1011,Expected '='
+1012,Expected '/'
+1013,Invalid number
+1014,Invalid character
+1015,Unterminated string constant
+1016,Unterminated comment
+1018,'return' statement outside of function
+1019,Can't have 'break' outside of loop
+1020,Can't have 'continue' outside of loop
+1023,Expected hexadecimal digit
+1024,Expected 'while'
+1025,Label redefined
+1026,Label not found
+1027,'default' can only appear once in a 'switch' statement
+1028,Expected identifier, string or number
+1029,Expected '@end'
+1030,Conditional compilation is turned off
+1031,Expected constant
+1032,Expected '@'
+1033,Expected 'catch'
+1034,Expected 'var'
+1035,'throw' must be followed by an expression on the same source line
+1037,'with' statements are not allowed in strict mode
+1038,Duplicate formal parameter names not allowed in strict mode
+1039,Octal numeric literals and escape characters not allowed in strict mode
+1041,Invalid usage of 'eval' in strict mode
+1042,Invalid usage of 'arguments' in strict mode
+1045,Calling delete on expression not allowed in strict mode
+1046,Multiple definitions of a property not allowed in strict mode
+1047,In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.
+1048,The use of a keyword for an identifier is invalid
+1049,The use of a future reserved word for an identifier is invalid
+1050,The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.
+1051,Setter functions must have one argument
+4096,JavaScript compilation error
+4097,JavaScript runtime error
+4098,Unknown runtime error
+5000,Cannot assign to 'this'
+5001,Number expected
+5002,Function expected
+5003,Cannot assign to a function result
+5004,Cannot index object
+5005,String expected
+5006,Date object expected
+5007,Object expected
+5008,Invalid left-hand side in assignment
+5009,Undefined identifier
+5010,Boolean expected
+5011,Can't execute code from a freed script
+5012,Object member expected
+5013,VBArray expected
+5014,JavaScript object expected
+5015,Enumerator object expected
+5016,Regular Expression object expected
+5017,Syntax error in regular expression
+5018,Unexpected quantifier
+5019,Expected ']' in regular expression
+5020,Expected ')' in regular expression
+5021,Invalid range in character set
+5022,Exception thrown and not caught
+5023,Function does not have a valid prototype object
+5024,The URI to be encoded contains an invalid character
+5025,The URI to be decoded is not a valid encoding
+5026,The number of fractional digits is out of range
+5027,The precision is out of range
+5028,Array or arguments object expected
+5029,Array length must be a finite positive integer
+5030,Array length must be assigned a finite positive number
+5031,Array object expected
+5034,Circular reference in value argument not supported
+5035,Invalid replacer argument
+5038,Argument list too large to apply
+5039,Redeclaration of const property
+5041,Object member not configurable
+5042,Variable undefined in strict mode
+5043,Accessing the 'caller' property of a function or arguments object is not allowed in strict mode
+5044,Accessing the 'callee' property of an arguments object is not allowed in strict mode
+5045,Assignment to read-only properties is not allowed in strict mode
+5046,Cannot create property for a non-extensible object
+5047,Object expected
+5048,Object expected
+5049,Object expected
+5050,Object expected
+5051,Function expected
+5052,Function expected
+5053,Property cannot have both accessors and a value
+5054,'this' is null or undefined
+5055,Object expected
+5056,Function expected
+5057,String expected
+5058,Boolean expected
+5059,Date expected
+5060,Number expected
+5061,VBArray expected
+5062,JavaScript object expected
+5063,Enumerator object expected
+5064,RegExp object expected
+5065,Invalid function argument
+5066,Object expected
+5067,JavaScript object expected
+5068,Function expected
+5069,VBArray expected
+5070,Object expected
+5071,Object expected
+5072,Invalid 'length' property
+5073,Array or arguments object expected
+5074,Invalid Operand
+5075,Invalid Operand
+5076,Invalid property descriptor
+5077,Cannot define property: object is not extensible
+5078,Cannot redefine non-configurable property
+5079,Cannot modify non-writable property
+5080,Cannot modify property: 'length' is not writable
+5081,Cannot define property
+5082,Typed array constructor argument is invalid
+5083,'this' is not a typed array object
+5084,Invalid offset/length when creating typed array
+5085,Invalid begin/end value in typed array subarray method
+5086,Invalid source in typed array set
+5087,'this' is not a DataView object
+5088,Invalid arguments in DataView
+5089,DataView operation access beyond specified buffer length
+5090,Invalid arguments in DataView
+5091,invalid function signature
+5092,invalid property signature
+5093,invalid input parameter type
+5094,invalid ouput parameter
+5095,Accessing the 'arguments' property of a function is not allowed in strict mode
+5096,Inspectable Object expected
+5097,Could not convert argument to type 'char'
+5098,Could not convert argument to type 'GUID'
+5099,IInspectable expected
+5100,Could not convert object to struct: object missing expected property
+5101,Unknown type
+5102,Function called with too few arguments
+5103,Type is not constructible
+5104,Could not convert value to PropertyValue: Type not supported by PropertyValue
+5105,Could not convert value to IInspectable: Type not supported by IInspectable
+5106,Could not convert Date to Windows.Foundation.DateTime: value outside of valid range
+5107,Could not convert value to Windows.Foundation.TimeSpan: value outside of valid range
+5108,Invalid access to already released Inspectable Object
+5109,Cannot release already released Inspectable Object
+5110,'this' is not of the expected type
+5111,Illegal length and size specified for the array
+5112,An unexpected failure occurred while trying to obtain metadata information
+5200,Status is 'error', but getResults did not return an error
+5201,Missing or invalid status parameter passed to completed handler
+5202,Missing or invalid sender parameter passed to completed handler
+6000,Infinity
+6001,-Infinity
+10438,Object doesn't support property or method '%s'
+10449,Argument to the function '%s' is not optional
+15001,'%s' is not a number
+15002,'%s' is not a function
+15004,'%s' is not an indexable object
+15005,'%s' is not a string
+15006,'%s' is not a date object
+15007,'%s' is null or not an object
+15008,Cannot assign to '%s'
+15009,'%s' is undefined
+15010,'%s' is not a boolean
+15012,Cannot delete '%s'
+15013,'%s' is not a VBArray
+15014,'%s' is not a JavaScript object
+15015,'%s' is not an enumerator object
+15016,'%s' is not a regular expression object
+15028,%s is not an Array or arguments object
+15031,%s is not an Array object
+15036,'%s' attribute on the property descriptor cannot be set to 'true' on this object
+15037,'%s' attribute on the property descriptor cannot be set to 'false' on this object
+15039,Redeclaration of const '%s'
+15041,Calling delete on '%s' is not allowed in strict mode
+15047,Unable to set property '%s' of undefined or null reference
+15048,Unable to get property '%s' of undefined or null reference
+15049,Unable to delete property '%s' of undefined or null reference
+15050,Unable to access property '%s': type 'VarDate' does not support user-defined properties
+15051,The value of the property '%s' is not a Function object
+15052,The value of the property '%s' is null or undefined, not a Function object
+15054,%s: 'this' is null or undefined
+15055,%s: 'this' is not an Object
+15056,%s: 'this' is not a Function object
+15057,%s: 'this' is not a String object
+15058,%s: 'this' is not a Boolean object
+15059,%s: 'this' is not a Date object
+15060,%s: 'this' is not a Number object
+15061,%s: 'this' is not a VBArray object
+15062,%s: 'this' is not a JavaScript object
+15063,%s: 'this' is not an Enumerator object
+15064,%s: 'this' is not a RegExp object
+15065,%s: invalid argument
+15066,%s: argument is not an Object
+15067,%s: argument is not a JavaScript object
+15068,%s: argument is not a Function object
+15069,%s: argument is not a VBArray object
+15070,%s: argument is null or undefined
+15071,%s: argument is not an Object and is not null
+15072,%s: argument does not have a valid 'length' property
+15073,%s: Array or arguments object expected
+15074,Invalid operand to '%s': Object expected
+15075,Invalid operand to '%s': Function expected
+15076,Invalid descriptor for property '%s'
+15077,Cannot define property '%s': object is not extensible
+15078,Cannot redefine non-configurable property '%s'
+15079,Cannot modify non-writable property '%s'
+15080,Cannot modify property '%s': 'length' is not writable
+15081,Cannot define property '%s'
+15088,Required argument %s in DataView method is not specified
+15090,DataView constructor argument %s is invalid
+15091,The function '%s' has an invalid signature and cannot be called
+15092,The property '%s' has an invalid signature and cannot be accessed
+15093,The runtimeclass %s that has Windows.Foundation.IPropertyValue as default interface is not supported as input parameter type
+15094,The object with interface Windows.Foundation.IPropertyValue that has runtimeclass name %s is not supported as out parameter
+15096,%s: 'this' is not an Inspectable Object
+15097,%s: could not convert argument to type 'char'
+15098,%s: could not convert argument to type 'GUID'
+15099,%s: could not convert return value to IInspectable
+15100,Could not convert object to struct: object missing expected property '%s'
+15101,Type '%s' not found
+15102,%s: function called with too few arguments
+15103,%s: type is not constructible
+15104,Could not convert value to PropertyValue: %s not supported by PropertyValue
+15105,Could not convert value to IInspectable: %s not supported by IInspectable
+15108,%s: The Inspectable object 'this' is released and cannot be accessed
+15110,'this' is not of expected type: %s
+15112,%s: an unexpected failure occurred while trying to obtain metadata information
+32812,The specified date is not available in the current locale's calendar
diff --git a/src/sentry/data/error-locale/tr-TR.txt b/src/sentry/data/error-locale/tr-TR.txt
new file mode 100644
index 00000000000000..c3cab29e12af39
--- /dev/null
+++ b/src/sentry/data/error-locale/tr-TR.txt
@@ -0,0 +1,289 @@
+5,Geçersiz yordam çağrısı veya değişken
+6,Taşma
+7,Yetersiz bellek
+9,Altsimge aralık dışı
+10,Bu dizi sabitlendi veya geçici olarak kilitlendi
+11,Sıfıra bölme
+13,Tür uyumsuz
+14,Karakter dizi alanı doldu
+17,İstenilen işlem yürütülemiyor
+28,Yığma alanı doldu
+35,Tanımsız Sub veya Function
+48,DLL yükleme hatası
+51,İç hata
+52,Yanlış dosya adı veya numarası
+53,Dosya bulunamadı
+54,Yanlış dosya modu
+55,Dosya zaten açık
+57,Aygıt G/Ç hatası
+58,Dosya var
+61,Disk dolu
+62,Giriş dosya sonunu geçti
+67,Çok fazla dosya
+68,Aygıt yok
+70,İzin verilmedi
+71,Disk hazır değil
+74,Farklı bir sürücü adına değiştirilemez
+75,Yol/Dosya erişim hatası
+76,Yol bulunamadı
+91,Object değişkeni veya With blok değişkeni ayarlanmamış
+92,For döngüsü başlatılmamış
+94,Geçersiz Null kullanımı
+322,Gerekli geçici dosya oluşturulamıyor
+424,Nesne gerekli
+429,Otomasyon sunucusu, nesne oluşturamıyor
+430,Sınıf, Otomasyon'u desteklemiyor
+432,Otomasyon işlemi boyunca dosya adı veya sınıf adı bulunamadı
+438,Nesne bu özellik veya yöntemi desteklemiyor
+440,Otomasyon hatası
+445,Nesne bu eylemi desteklemiyor
+446,Nesne adlandırılmış bağımsız değişkeni desteklemiyor
+447,Nesne geçerli yerel ayarları desteklemiyor
+448,Adlandırılmış bağımsız değişken bulunamadı
+449,Bağımsız değişken isteğe bağlı değil
+450,Hatalı bağımsız değişken numarası veya geçersiz özellik ataması
+451,Nesne koleksiyon değil
+453,Belirtilen DLL işlevi bulunamadı
+458,Değişken, JavaScript'te desteklenmeyen bir Otomasyon türü kullanıyor
+462,Uzak sunucu makinesi yok veya kullanılabilir değil
+501,Değişkene atanamıyor
+502,Nesne, yazım için güvenli değil
+503,Nesne, başlatma için güvenli değil
+504,Nesne yaratmak için güvenli değil
+507,Özel bir durum oluştu
+1001,Yetersiz bellek
+1002,Sözdizimi hatası
+1003,':' gerekli
+1004,';' gerekli
+1005,'(' gerekli
+1006,')' gerekli
+1007,']' gerekli
+1008,'{' gerekli
+1009,'}' gerekli
+1010,Belirleyici gerekli
+1011,'=' bekleniyor
+1012,'/' bekleniyor
+1013,Geçersiz sayı
+1014,Geçersiz karakter
+1015,Sonlandırılmamış dize sabiti
+1016,Sonlandırılmamış açıklama
+1018,işlevin dışında 'return' deyimi
+1019,Döngünün dışında 'break' olamaz
+1020,Döngünün dışında 'continue' olamaz
+1023,Onaltılık hane bekleniyor
+1024,'while' bekleniyor
+1025,Etiket yeniden tanımlandı
+1026,Etiket bulunamadı
+1027,'default', 'switch' bildirisinde yalnız bir kere görünebilir
+1028,Tanımlayıcı, dize veya sayı bekleniyor
+1029,'@end' bekleniyor
+1030,Koşullu derleme kapalı
+1031,Sabit bekleniyor
+1032,'@' bekleniyor
+1033,'catch' bekleniyor
+1034,'var' bekleniyor
+1035,'throw' yönergesinden sonra aynı kaynak satırında bir ifade gelmeli
+1037,'with' deyimlerinin katı modda kullanılmasına izin verilmez
+1038,Yinelenen biçimsel parametre adlarına katı modda izin verilmez
+1039,Sekizli sayısal değişmez değerlere ve kaçış karakterlerine katı modda izin verilmez
+1041,Katı modda 'eval' öğesinin geçersiz kullanımı
+1042,Katı modda 'arguments' öğesinin geçersiz kullanımı
+1045,İfadede silme işlevini çağırmaya katı modda izin verilmez
+1046,Bir özelliğin birden fazla tanımına katı modda izin verilmez
+1047,Katı modda, işlev bildirimleri bir deyim veya blok içinde iç içe olamaz. Yalnızca en üst düzeyde veya doğrudan işlev gövdesinin içinde görünebilirler.
+1048,Bir tanımlayıcının anahtar sözcük kullanımı geçersiz
+1049,Bir tanımlayıcının ileride kullanılmak üzere ayrılmış sözcük kullanımı geçersiz
+1050,Bir tanımlayıcının ileride kullanılmak üzere ayrılmış sözcük kullanımı geçersiz. Tanımlayıcı adı katı modda ayrılmış.
+1051,Ayarlayıcı işlevleri bir bağımsız değişken içermelidir
+4096,JavaScript derleme hatası
+4097,JavaScript çalışma zamanı hatası
+4098,Bilinmeyen çalışma hatası
+5000,'this' için atama yapılamıyor
+5001,Sayı bekleniyor
+5002,İşlev bekleniyor
+5003,İşlev sonucuna atanamaz
+5004,Nesne dizilemiyor
+5005,Dize bekleniyor
+5006,Tarih nesnesi bekleniyor
+5007,Nesne bekleniyor
+5008,Atama içinde sol taraf geçersiz
+5009,Tanımsız belirleyici
+5010,Boolean bekleniyor
+5011,Boşaltılmış komut dosyasından kod çalıştırılamıyor
+5012,Nesne üyesi bekleniyor
+5013,VBArray bekleniyor
+5014,JavaScript nesnesi bekleniyor
+5015,Numaralandırıcı nesne bekleniyor
+5016,Normal Deyim nesnesi bekleniyor
+5017,Normal deyimde yazım hatası
+5018,Beklenmeyen miktar
+5019,Normal deyimde ']' bekleniyor
+5020,Normal deyimde ')' bekleniyor
+5021,Geçersiz karakter kümesi aralığı
+5022,Olağan dışı durum oluşturuldu (throw ile) ve yakalanmadı (catch ile)
+5023,İşlevin geçerli bir prototip nesnesi yok
+5024,Kodlanacak URI geçersiz bir karakter içeriyor
+5025,Kodu çözülecek URI geçerli bir kodlama değil
+5026,Kesir basamak sayısı aralık dışında
+5027,Duyarlılık aralık dışında
+5028,Array veya arguments nesnesi bekleniyor
+5029,Dizi uzunluğu sonlu pozitif bir tamsayı olmalıdır
+5030,Dizi uzunluğu sonlu pozitif bir tamsayıya atanmalıdır
+5031,Dizi nesnesi bekleniyor
+5034,Değer değişkenindeki döngüsel başvuru desteklenmiyor
+5035,Geçersiz değiştirici bağımsız değişkeni
+5038,Bağımsız değişken listesi uygulamak için çok büyük
+5039,Sabit özelliğinin yeniden bildirimi
+5041,Nesne üyesi yapılandırılabilir değil
+5042,Katı modda değişken tanımsız
+5043,İşlevin veya bağımsız değişkenlerinin 'caller' özelliğine erişimine katı modda izin verilmez
+5044,İşlevin veya bağımsız değişkenlerinin 'callee' özelliğine erişimine katı modda izin verilmez
+5045,Salt okunur özelliklerin atanmasına katı modda izin verilmez
+5046,Genişletilebilir olmayan bir nesne için özellik oluşturulamıyor
+5047,Nesne bekleniyor
+5048,Nesne bekleniyor
+5049,Nesne bekleniyor
+5050,Nesne bekleniyor
+5051,İşlev bekleniyor
+5052,İşlev bekleniyor
+5053,Özelliğin hem erişimcisi hem de değeri olamaz
+5054,'this' boş veya tanımsız
+5055,Nesne bekleniyor
+5056,İşlev bekleniyor
+5057,Dize bekleniyor
+5058,Boole bekleniyor
+5059,Tarih bekleniyor
+5060,Sayı bekleniyor
+5061,VBArray bekleniyor
+5062,JavaScript nesnesi bekleniyor
+5063,Numaralandırıcı nesne bekleniyor
+5064,RegExp nesnesi bekleniyor
+5065,Geçersiz işlev bağımsız değişkeni
+5066,Nesne bekleniyor
+5067,JavaScript nesnesi bekleniyor
+5068,İşlev bekleniyor
+5069,VBArray bekleniyor
+5070,Nesne bekleniyor
+5071,Nesne bekleniyor
+5072,Geçersiz 'length' özelliği
+5073,Array veya arguments nesnesi bekleniyor
+5074,Geçersiz İşlenen
+5075,Geçersiz İşlenen
+5076,Geçersiz özellik tanımlayıcısı
+5077,Özellik tanımlanamıyor: nesne genişletilebilir değil
+5078,Yapılandırılabilir olmayan özellik yeniden tanımlanamıyor
+5079,Yazılabilir olmayan özellik değiştirilemiyor
+5080,Özellik değiştirilemiyor: 'length' yazılabilir değil
+5081,Özellik tanımlanamıyor
+5082,Türü örtük olarak belirlenmiş dizinin kurucu bağımsız değişkeni geçersiz
+5083,'this' türü örtük olarak belirlenmiş dizi nesnesi değil
+5084,Türü örtük olarak belirlenmiş dizi oluşturulurken geçersiz uzaklık/uzunluk
+5085,Türü örtük olarak belirlenmiş dizi alt dizi yönteminde geçersiz başlangıç/bitiş değeri
+5086,Türü örtük olarak belirlenmiş dizi kümesinde geçersiz kaynak
+5087,'this' bir DataView nesnesi değil
+5088,DataView içinde geçersiz bağımsız değişkenler
+5089,DataView işlemi belirtilen arabellek uzunluğunun dışına erişiyor
+5090,DataView içinde geçersiz bağımsız değişkenler
+5091,geçersiz işlev imzası
+5092,geçersiz özellik imzası
+5093,geçersiz giriş parametresi türü
+5094,geçersiz çıkış parametresi
+5095,İşlevin 'arguments' özelliğine erişmeye katı modda izin verilmez
+5096,İncelenebilir Nesne bekleniyordu
+5097,Bağımsız değişken 'char' türüne dönüştürülemedi
+5098,Bağımsız değişken 'GUID' türüne dönüştürülemedi
+5099,IInspectable olması bekleniyordu
+5100,Nesne yapı birimine dönüştürülemedi: nesnede beklenen özellik yok
+5101,Bilinmeyen tür
+5102,İşlev çok az bağımsız değişkenle çağrıldı
+5103,Tür, oluşturulabilir bir tür değil
+5104,Değer PropertyValue değerine dönüştürülemedi: Tür PropertyValue tarafından desteklenmiyor
+5105,Değer IInspectable değerine dönüştürülemedi: Tür IInspectable tarafından desteklenmiyor
+5106,Tarih Windows.Foundation.DateTime biçimine dönüştürülemedi: değer geçerli aralığın dışında
+5107,Değer Windows.Foundation.TimeSpan biçimine dönüştürülemedi: değer geçerli aralığın dışında
+5108,Serbest bırakılan Inspectable Nesnesi erişimi geçersiz
+5109,Serbest bırakılmış olan Inspectable Nesnesi serbest bırakılamıyor
+5110,'bu' beklenen tür değil
+5111,Dize için belirtilen geçersiz uzunluk ve boyut
+5112,Meta veri bilgilerini almaya çalışırken beklenmeyen bir hata oluştu
+5200,Durum: 'hata' ancak getResults hata döndürmedi
+5201,Eksik veya geçersiz durum parametresi tamamlanan işleyiciye geçirildi
+5202,Eksik veya geçersiz gönderen parametresi tamamlanan işleyiciye geçirildi
+6000,Sonsuz
+6001,-Sonsuz
+10438,Nesne, '%s' özelliğini veya yöntemini desteklemez
+10449,'%s' işlevinin bağımsız değişkeni isteğe bağlı değil
+15001,'%s', bir sayı değil
+15002,'%s', bir işlev değil
+15004,'%s', dizilebilir bir nesne değil
+15005,'%s', bir dize değil
+15006,'%s', bir tarih nesnesi değil
+15007,'%s', boş veya bir nesne değil
+15008,'%s' üzerine atama yapılamıyor
+15009,'%s' tanımsız
+15010,'%s', boolean değil
+15012,'%s' silinemiyor
+15013,'%s', VBArray değil
+15014,'%s' JavaScript nesnesi değil
+15015,'%s' numaralandırıcı nesne değil
+15016,'%s' normal bir ifade nesnesi değil
+15028,%s, Array veya arguments nesnesi değil
+15031,%s, Array nesnesi değil
+15036,Özellik tanımlayıcısındaki '%s' özniteliği, bu nesnede 'true' olarak ayarlanamaz
+15037,Özellik tanımlayıcısındaki '%s' özniteliği, bu nesnede 'false' olarak ayarlanamaz
+15039,'%s' sabitinin yeniden bildirimi
+15041,'%s' üzerinde silme işlevini çağırmaya katı modda izin verilmez
+15047,Tanımsız veya boş referansın '%s' özelliği ayarlanamıyor
+15048,Tanımsız veya boş referansın '%s' özelliği alınamıyor
+15049,Tanımsız veya boş referansın '%s' özelliği silinemiyor
+15050,'%s' özelliğine erişilemiyor: 'VarDate' türü kullanıcı tanımlı özellikleri desteklemiyor
+15051,'%s' özelliğinin değeri İşlev nesnesi değil
+15052,'%s' özelliğinin değeri boş veya tanımsız, İşlev nesnesi değil
+15054,%s: 'this' boş veya tanımsız
+15055,%s: 'this' bir Nesne değil
+15056,%s: 'this' bir İşlev nesnesi değil
+15057,%s: 'this' bir Dize nesnesi değil
+15058,%s: 'this' bir Boole nesnesi değil
+15059,%s: 'this' bir Tarih nesnesi değil
+15060,%s: 'this' bir Sayı nesnesi değil
+15061,%s: 'this' bir VBArray nesnesi değil
+15062,%s: 'this' bir JavaScript nesnesi değil
+15063,%s: 'this' bir Numaralandırıcı nesne değil
+15064,%s: 'this' bir RegExp nesnesi değil
+15065,%s: geçersiz bağımsız değişken
+15066,%s: bağımsız değişken bir Nesne değil
+15067,%s: bağımsız değişken bir JavaScript nesnesi değil
+15068,%s: bağımsız değişken bir İşlev nesnesi değil
+15069,%s: bağımsız değişken bir VBArray nesnesi değil
+15070,%s: bağımsız değişken boş veya tanımsız
+15071,%s: bağımsız değişken bir Nesne değil ve boş değil
+15072,%s: bağımsız değişkenin geçerli bir 'length' özelliği yok
+15073,%s: Array veya arguments nesnesi bekleniyor
+15074,'%s' için geçersiz işlenen: Nesne bekleniyor
+15075,'%s' için geçersiz işlenen: İşlev bekleniyor
+15076,'%s' özelliği için geçersiz tanımlayıcı
+15077,'%s' özelliği tanımlanamıyor: nesne genişletilebilir değil
+15078,Yapılandırılabilir olmayan '%s' özelliği yeniden tanımlanamıyor
+15079,Yazılabilir olmayan '%s' özelliği değiştirilemiyor
+15080,'%s' özelliği değiştirilemiyor: 'length' yazılabilir değil
+15081,'%s' özelliği tanımlanamıyor
+15088,DataView yönteminde gereken %s bağımsız değişkeni belirtilmedi
+15090,%s DataView kurucu bağımsız değişkeni geçersiz
+15091,'%s' işlevinde geçersiz bir imza var ve çağrılamıyor
+15092,'%s' özelliğinde geçersiz bir imza var ve erişilemiyor
+15093,Varsayılan arabirimi Windows.Foundation.IPropertyValue olan %s çalışma zamanı sınıfı, giriş parametresi türü olarak desteklenmiyor
+15094,Çalışma zamanı sınıfı adı %s olan Windows.Foundation.IPropertyValue arabirimine sahip nesne, çıkış parametresi olarak desteklenmiyor
+15096,%s: 'this' bir Inspectable Nesnesi değil
+15097,%s: bağımsız değişken 'char' türüne dönüştürülemedi
+15098,%s: bağımsız değişken 'GUID' türüne dönüştürülemedi
+15099,%s: dönen değer IInspectable değerine dönüştürülemedi
+15100,Nesne yapı birimine dönüştürülemedi: nesnede beklenen '%s' özelliği yok
+15101,'%s' türü bulunamadı
+15102,%s: işlev çok az bağımsız değişkenle çağrıldı
+15103,%s: tür, oluşturulabilir bir tür değil
+15104,Değer PropertyValue değerine dönüştürülemedi: %s PropertyValue tarafından desteklenmiyor
+15105,Değer IInspectable değerine dönüştürülemedi: %s IInspectable tarafından desteklenmiyor
+15108,%s: 'this' Inspectable nesnesi serbest bırakıldı ve erişilemiyor
+15110,'bu' beklenen tür değil: %s
+15112,%s: meta veri bilgilerini almaya çalışırken beklenmeyen bir hata oluştu
+32812,Belirtilen tarih şu anki yerin takviminde kullanılamaz
diff --git a/src/sentry/data/error-locale/uk-UA.txt b/src/sentry/data/error-locale/uk-UA.txt
new file mode 100644
index 00000000000000..6580983d9b4a88
--- /dev/null
+++ b/src/sentry/data/error-locale/uk-UA.txt
@@ -0,0 +1,289 @@
+5,Invalid procedure call or argument
+6,Overflow
+7,Out of memory
+9,Subscript out of range
+10,This array is fixed or temporarily locked
+11,Division by zero
+13,Type mismatch
+14,Out of string space
+17,Can't perform requested operation
+28,Out of stack space
+35,Sub or Function not defined
+48,Error in loading DLL
+51,Internal error
+52,Bad file name or number
+53,File not found
+54,Bad file mode
+55,File already open
+57,Device I/O error
+58,File already exists
+61,Disk full
+62,Input past end of file
+67,Too many files
+68,Device unavailable
+70,Permission denied
+71,Disk not ready
+74,Can't rename with different drive
+75,Path/File access error
+76,Path not found
+91,Object variable or With block variable not set
+92,For loop not initialized
+94,Invalid use of Null
+322,Can't create necessary temporary file
+424,Object required
+429,Automation server can't create object
+430,Class doesn't support Automation
+432,File name or class name not found during Automation operation
+438,Object doesn't support this property or method
+440,Automation error
+445,Object doesn't support this action
+446,Object doesn't support named arguments
+447,Object doesn't support current locale setting
+448,Named argument not found
+449,Argument not optional
+450,Wrong number of arguments or invalid property assignment
+451,Object not a collection
+453,Specified DLL function not found
+458,Variable uses an Automation type not supported in JavaScript
+462,The remote server machine does not exist or is unavailable
+501,Cannot assign to variable
+502,Object not safe for scripting
+503,Object not safe for initializing
+504,Object not safe for creating
+507,An exception occurred
+1001,Out of memory
+1002,Syntax error
+1003,Expected ':'
+1004,Expected ';'
+1005,Expected '('
+1006,Expected ')'
+1007,Expected ']'
+1008,Expected '{'
+1009,Expected '}'
+1010,Expected identifier
+1011,Expected '='
+1012,Expected '/'
+1013,Invalid number
+1014,Invalid character
+1015,Unterminated string constant
+1016,Unterminated comment
+1018,'return' statement outside of function
+1019,Can't have 'break' outside of loop
+1020,Can't have 'continue' outside of loop
+1023,Expected hexadecimal digit
+1024,Expected 'while'
+1025,Label redefined
+1026,Label not found
+1027,'default' can only appear once in a 'switch' statement
+1028,Expected identifier, string or number
+1029,Expected '@end'
+1030,Conditional compilation is turned off
+1031,Expected constant
+1032,Expected '@'
+1033,Expected 'catch'
+1034,Expected 'var'
+1035,'throw' must be followed by an expression on the same source line
+1037,'with' statements are not allowed in strict mode
+1038,Duplicate formal parameter names not allowed in strict mode
+1039,Octal numeric literals and escape characters not allowed in strict mode
+1041,Invalid usage of 'eval' in strict mode
+1042,Invalid usage of 'arguments' in strict mode
+1045,Calling delete on expression not allowed in strict mode
+1046,Multiple definitions of a property not allowed in strict mode
+1047,In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.
+1048,The use of a keyword for an identifier is invalid
+1049,The use of a future reserved word for an identifier is invalid
+1050,The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.
+1051,Setter functions must have one argument
+4096,JavaScript compilation error
+4097,JavaScript runtime error
+4098,Unknown runtime error
+5000,Cannot assign to 'this'
+5001,Number expected
+5002,Function expected
+5003,Cannot assign to a function result
+5004,Cannot index object
+5005,String expected
+5006,Date object expected
+5007,Object expected
+5008,Invalid left-hand side in assignment
+5009,Undefined identifier
+5010,Boolean expected
+5011,Can't execute code from a freed script
+5012,Object member expected
+5013,VBArray expected
+5014,JavaScript object expected
+5015,Enumerator object expected
+5016,Regular Expression object expected
+5017,Syntax error in regular expression
+5018,Unexpected quantifier
+5019,Expected ']' in regular expression
+5020,Expected ')' in regular expression
+5021,Invalid range in character set
+5022,Exception thrown and not caught
+5023,Function does not have a valid prototype object
+5024,The URI to be encoded contains an invalid character
+5025,The URI to be decoded is not a valid encoding
+5026,The number of fractional digits is out of range
+5027,The precision is out of range
+5028,Array or arguments object expected
+5029,Array length must be a finite positive integer
+5030,Array length must be assigned a finite positive number
+5031,Array object expected
+5034,Circular reference in value argument not supported
+5035,Invalid replacer argument
+5038,Argument list too large to apply
+5039,Redeclaration of const property
+5041,Object member not configurable
+5042,Variable undefined in strict mode
+5043,Accessing the 'caller' property of a function or arguments object is not allowed in strict mode
+5044,Accessing the 'callee' property of an arguments object is not allowed in strict mode
+5045,Assignment to read-only properties is not allowed in strict mode
+5046,Cannot create property for a non-extensible object
+5047,Object expected
+5048,Object expected
+5049,Object expected
+5050,Object expected
+5051,Function expected
+5052,Function expected
+5053,Property cannot have both accessors and a value
+5054,'this' is null or undefined
+5055,Object expected
+5056,Function expected
+5057,String expected
+5058,Boolean expected
+5059,Date expected
+5060,Number expected
+5061,VBArray expected
+5062,JavaScript object expected
+5063,Enumerator object expected
+5064,RegExp object expected
+5065,Invalid function argument
+5066,Object expected
+5067,JavaScript object expected
+5068,Function expected
+5069,VBArray expected
+5070,Object expected
+5071,Object expected
+5072,Invalid 'length' property
+5073,Array or arguments object expected
+5074,Invalid Operand
+5075,Invalid Operand
+5076,Invalid property descriptor
+5077,Cannot define property: object is not extensible
+5078,Cannot redefine non-configurable property
+5079,Cannot modify non-writable property
+5080,Cannot modify property: 'length' is not writable
+5081,Cannot define property
+5082,Typed array constructor argument is invalid
+5083,'this' is not a typed array object
+5084,Invalid offset/length when creating typed array
+5085,Invalid begin/end value in typed array subarray method
+5086,Invalid source in typed array set
+5087,'this' is not a DataView object
+5088,Invalid arguments in DataView
+5089,DataView operation access beyond specified buffer length
+5090,Invalid arguments in DataView
+5091,invalid function signature
+5092,invalid property signature
+5093,invalid input parameter type
+5094,invalid ouput parameter
+5095,Accessing the 'arguments' property of a function is not allowed in strict mode
+5096,Inspectable Object expected
+5097,Could not convert argument to type 'char'
+5098,Could not convert argument to type 'GUID'
+5099,IInspectable expected
+5100,Could not convert object to struct: object missing expected property
+5101,Unknown type
+5102,Function called with too few arguments
+5103,Type is not constructible
+5104,Could not convert value to PropertyValue: Type not supported by PropertyValue
+5105,Could not convert value to IInspectable: Type not supported by IInspectable
+5106,Could not convert Date to Windows.Foundation.DateTime: value outside of valid range
+5107,Could not convert value to Windows.Foundation.TimeSpan: value outside of valid range
+5108,Invalid access to already released Inspectable Object
+5109,Cannot release already released Inspectable Object
+5110,'this' is not of the expected type
+5111,Illegal length and size specified for the array
+5112,An unexpected failure occurred while trying to obtain metadata information
+5200,Status is 'error', but getResults did not return an error
+5201,Missing or invalid status parameter passed to completed handler
+5202,Missing or invalid sender parameter passed to completed handler
+6000,Infinity
+6001,-Infinity
+10438,Object doesn't support property or method '%s'
+10449,Argument to the function '%s' is not optional
+15001,'%s' is not a number
+15002,'%s' is not a function
+15004,'%s' is not an indexable object
+15005,'%s' is not a string
+15006,'%s' is not a date object
+15007,'%s' is null or not an object
+15008,Cannot assign to '%s'
+15009,'%s' is undefined
+15010,'%s' is not a boolean
+15012,Cannot delete '%s'
+15013,'%s' is not a VBArray
+15014,'%s' is not a JavaScript object
+15015,'%s' is not an enumerator object
+15016,'%s' is not a regular expression object
+15028,%s is not an Array or arguments object
+15031,%s is not an Array object
+15036,'%s' attribute on the property descriptor cannot be set to 'true' on this object
+15037,'%s' attribute on the property descriptor cannot be set to 'false' on this object
+15039,Redeclaration of const '%s'
+15041,Calling delete on '%s' is not allowed in strict mode
+15047,Unable to set property '%s' of undefined or null reference
+15048,Unable to get property '%s' of undefined or null reference
+15049,Unable to delete property '%s' of undefined or null reference
+15050,Unable to access property '%s': type 'VarDate' does not support user-defined properties
+15051,The value of the property '%s' is not a Function object
+15052,The value of the property '%s' is null or undefined, not a Function object
+15054,%s: 'this' is null or undefined
+15055,%s: 'this' is not an Object
+15056,%s: 'this' is not a Function object
+15057,%s: 'this' is not a String object
+15058,%s: 'this' is not a Boolean object
+15059,%s: 'this' is not a Date object
+15060,%s: 'this' is not a Number object
+15061,%s: 'this' is not a VBArray object
+15062,%s: 'this' is not a JavaScript object
+15063,%s: 'this' is not an Enumerator object
+15064,%s: 'this' is not a RegExp object
+15065,%s: invalid argument
+15066,%s: argument is not an Object
+15067,%s: argument is not a JavaScript object
+15068,%s: argument is not a Function object
+15069,%s: argument is not a VBArray object
+15070,%s: argument is null or undefined
+15071,%s: argument is not an Object and is not null
+15072,%s: argument does not have a valid 'length' property
+15073,%s: Array or arguments object expected
+15074,Invalid operand to '%s': Object expected
+15075,Invalid operand to '%s': Function expected
+15076,Invalid descriptor for property '%s'
+15077,Cannot define property '%s': object is not extensible
+15078,Cannot redefine non-configurable property '%s'
+15079,Cannot modify non-writable property '%s'
+15080,Cannot modify property '%s': 'length' is not writable
+15081,Cannot define property '%s'
+15088,Required argument %s in DataView method is not specified
+15090,DataView constructor argument %s is invalid
+15091,The function '%s' has an invalid signature and cannot be called
+15092,The property '%s' has an invalid signature and cannot be accessed
+15093,The runtimeclass %s that has Windows.Foundation.IPropertyValue as default interface is not supported as input parameter type
+15094,The object with interface Windows.Foundation.IPropertyValue that has runtimeclass name %s is not supported as out parameter
+15096,%s: 'this' is not an Inspectable Object
+15097,%s: could not convert argument to type 'char'
+15098,%s: could not convert argument to type 'GUID'
+15099,%s: could not convert return value to IInspectable
+15100,Could not convert object to struct: object missing expected property '%s'
+15101,Type '%s' not found
+15102,%s: function called with too few arguments
+15103,%s: type is not constructible
+15104,Could not convert value to PropertyValue: %s not supported by PropertyValue
+15105,Could not convert value to IInspectable: %s not supported by IInspectable
+15108,%s: The Inspectable object 'this' is released and cannot be accessed
+15110,'this' is not of expected type: %s
+15112,%s: an unexpected failure occurred while trying to obtain metadata information
+32812,The specified date is not available in the current locale's calendar
diff --git a/src/sentry/data/error-locale/zh-CN.txt b/src/sentry/data/error-locale/zh-CN.txt
new file mode 100644
index 00000000000000..122549f441dd93
--- /dev/null
+++ b/src/sentry/data/error-locale/zh-CN.txt
@@ -0,0 +1,289 @@
+5,无效的过程调用或参数
+6,溢出
+7,内存不足
+9,下标越界
+10,该数组为定长的或临时被锁定
+11,被零除
+13,类型不匹配
+14,字符串空间不够
+17,不能执行所需的操作
+28,堆栈溢出
+35,未定义 Sub 或 Function
+48,加载 DLL 时出错
+51,内部错误
+52,错误的文件名或号码
+53,文件未找到
+54,错误的文件模式
+55,文件已经打开
+57,设备 I/O 错误
+58,文件已存在
+61,磁盘已满
+62,输入超出了文件尾
+67,文件过多
+68,设备不可用
+70,没有权限
+71,磁盘没有准备好
+74,重命名时不能带有其他驱动器符号
+75,路径/文件访问错误
+76,路径未找到
+91,对象变量或 With 块变量未设置
+92,For 循环未初始化
+94,无效使用 Null
+322,不能创建所需的临时文件
+424,缺少对象
+429,Automation 服务器不能创建对象
+430,类不能支持 Automation 操作
+432,Automation 操作中文件名或类名未找到
+438,对象不支持此属性或方法
+440,Automation 操作错误
+445,对象不支持此操作
+446,对象不支持已命名参数
+447,对象不支持当前区域设置选项
+448,未找到已命名参数
+449,参数是必选项
+450,错误的参数个数或无效的参数属性值
+451,对象不是一个集合
+453,未找到指定的 DLL 函数
+458,变量使用了一个 JavaScript 中不支持的 Automation 类型
+462,远程服务器不存在或不可用
+501,不能给变量赋值
+502,对象不能安全地使用 Script 编程
+503,对象不能安全初始化
+504,对象不能安全创建
+507,出现一个意外错误
+1001,内存不足
+1002,语法错误
+1003,缺少 ':'
+1004,缺少 ';'
+1005,缺少 '('
+1006,缺少 ')'
+1007,缺少 ']'
+1008,缺少 '{'
+1009,缺少 '}'
+1010,缺少标识符
+1011,缺少 '='
+1012,缺少 '/'
+1013,无效数字
+1014,无效字符
+1015,未结束的字符串常量
+1016,注释未结束
+1018,'return' 语句在函数之外
+1019,循环之外不能有 'break'
+1020,循环之外不能有 'continue'
+1023,缺少十六进制数字
+1024,缺少 'while'
+1025,标签重复定义
+1026,标签未找到
+1027,'default' 只能在 'switch' 语句中出现一次
+1028,缺少标识符、字符串或数字
+1029,缺少 '@end'
+1030,条件编译已关闭
+1031,缺少常量
+1032,缺少 '@'
+1033,缺少 'catch'
+1034,缺少 'var'
+1035,“throw”的后面必须在同一行跟有一个表达式
+1037,严格模式下不允许使用“with”语句
+1038,严格模式下不允许正式参数名称重复
+1039,严格模式下不允许使用八进制数字参数和转义字符
+1041,严格模式下“eval”的用法无效
+1042,严格模式下“arguments”的用法无效
+1045,严格模式下不允许对表达式调用 Delete
+1046,严格模式下不允许一个属性有多个定义
+1047,在严格模式下,函数声明无法嵌套在语句或块内。这些声明仅出现在顶级或直接出现在函数体内。
+1048,对标识符使用关键字无效
+1049,对标识符使用日后保留字词无效
+1050,对标识符使用日后保留字词无效。在严格模式下会保留标识符名称。
+1051,Setter 函数必须具有一个参数
+4096,JavaScript 编译错误
+4097,JavaScript 运行时错误
+4098,未知的运行时错误
+5000,不能给 'this' 赋值
+5001,缺少数字
+5002,缺少函数
+5003,不能给函数返回值赋值
+5004,不能检索对象
+5005,缺少字符串
+5006,缺少日期对象
+5007,缺少对象
+5008,赋值左侧无效
+5009,未定义标识符
+5010,缺少布尔变量
+5011,不能执行已释放 Script 的代码
+5012,缺少对象成员
+5013,缺少 VBArray
+5014,缺少 JavaScript 对象
+5015,缺少枚举器对象
+5016,缺少正则表达式对象
+5017,正则表达式语法错误
+5018,错误的数量词
+5019,正则表达式中缺少 ']'
+5020,正则表达式中缺少 ')'
+5021,字符集越界
+5022,引发了异常但未捕获
+5023,函数没有有效的原型对象
+5024,被编码的 URI 中包含非法字符
+5025,被解码的 URI 不是合法的编码
+5026,小数位数字越界
+5027,精度越界
+5028,缺少 Array 或 arguments 对象
+5029,数组长度必须为有限正整数
+5030,数组长度必须赋值为有限正数
+5031,缺少 Array 对象
+5034,不支持在值参数中进行循环引用
+5035,无效的替换器参数
+5038,参数列表太大,无法应用
+5039,重新声明常量属性
+5041,无法配置对象成员
+5042,严格模式下未定义变量
+5043,严格模式下不允许访问函数或参数对象的“caller”属性
+5044,严格模式下不允许访问参数对象的“callee”属性
+5045,严格模式下不允许分配到只读属性
+5046,无法为不可扩展的对象创建属性
+5047,缺少对象
+5048,缺少对象
+5049,缺少对象
+5050,缺少对象
+5051,缺少函数
+5052,缺少函数
+5053,属性不能同时具有取值函数和值
+5054,'this' 为 null 或未定义
+5055,缺少对象
+5056,缺少函数
+5057,缺少字符串
+5058,缺少布尔变量
+5059,缺少日期
+5060,缺少数字
+5061,缺少 VBArray
+5062,缺少 JavaScript 对象
+5063,缺少枚举器对象
+5064,缺少 RegExp 对象
+5065,函数参数无效
+5066,缺少对象
+5067,缺少 JavaScript 对象
+5068,缺少函数
+5069,缺少 VBArray
+5070,缺少对象
+5071,缺少对象
+5072,“length”属性无效
+5073,缺少 Array 或 arguments 对象
+5074,操作数无效
+5075,操作数无效
+5076,属性描述符无效
+5077,无法定义属性: 对象不可扩展
+5078,无法重新定义不可配置的属性
+5079,无法修改不可写的属性
+5080,无法修改属性:“length”不可写
+5081,无法定义属性
+5082,类型化数组构造函数参数无效
+5083,“this”不是类型化数组对象
+5084,在创建类型化数组时,偏移/长度无效
+5085,类型化数组子数组方法中的开始/结束值无效
+5086,类型化数组集中的源无效
+5087,“this”不是 DataView 对象
+5088,DataView 中的参数无效
+5089,DataView 操作访问超出指定的缓冲区长度
+5090,DataView 中的参数无效
+5091,无效的函数签名
+5092,无效的属性签名
+5093,输入参数类型无效
+5094,输出参数无效
+5095,在严格模式下不允许访问函数的 "arguments" 属性
+5096,应为 Inspectable 对象
+5097,无法将参数转换为 "char" 类型
+5098,无法将参数转换为 "GUID" 类型
+5099,应为 IInspectable
+5100,无法将对象转换为结构: 该对象缺少应有的属性
+5101,未知类型
+5102,使用过少的参数调用函数
+5103,类型不可构造
+5104,无法将值转换为 PropertyValue: PropertyValue 不支持类型
+5105,无法将值转换为 IInspectable: IInspectable 不支持类型
+5106,无法将 Date 转换为 Windows.Foundation.DateTime: 值超出有效范围
+5107,无法将值转换为 Windows.Foundation.TimeSpan: 值超出有效范围
+5108,对已释放的 Inspectable 对象的访问无效
+5109,无法释放已释放的 Inspectable 对象
+5110,'this' 不是所需的类型
+5111,为数组指定的长度和大小无效
+5112,尝试获取元数据信息时发生意外故障
+5200,状态为 'error',但 getResults 未返回错误
+5201,传递给完成的处理程序的状态参数丢失或无效
+5202,传递给完成的处理程序的发送方参数丢失或无效
+6000,正无穷大
+6001,负无穷大
+10438,对象不支持“%s”属性或方法
+10449,函数“%s”的参数是必需的
+15001,“%s”不是数字
+15002,“%s”不是函数
+15004,“%s”不是可索引的对象
+15005,“%s”不是字符串
+15006,“%s”不是日期对象
+15007,“%s”为 null 或不是对象
+15008,不能给“%s”赋值
+15009,“%s”未定义
+15010,“%s”不是布尔变量
+15012,不能删除“%s”
+15013,“%s”不是 VBArray
+15014,“%s”不是 JavaScript 对象
+15015,“%s”不是枚举器对象
+15016,“%s”不是正则表达式对象
+15028,%s 不是 Array 或 arguments 对象
+15031,%s 不是 Array 对象
+15036,此对象的属性描述器的特性“%s”不能设置为“true”
+15037,此对象的属性描述器的特性“%s”不能设置为“false”
+15039,重新声明常量“%s”
+15041,严格模式下不允许对“%s”调用 Delete
+15047,无法设置未定义或 null 引用的属性“%s”
+15048,无法获取未定义或 null 引用的属性“%s”
+15049,无法删除未定义或 null 引用的属性“%s”
+15050,无法访问属性“%s”: 类型“VarDate”不支持用户定义的属性
+15051,属性“%s”的值不是 Function 对象
+15052,属性“%s”的值为 null、未定义或不是 Function 对象
+15054,%s: 'this' 为 null 或未定义
+15055,%s: 'this' 不是 Object
+15056,%s: 'this' 不是 Function 对象
+15057,%s: 'this' 不是 String 对象
+15058,%s: 'this' 不是 Boolean 对象
+15059,%s: 'this' 不是 Date 对象
+15060,%s: 'this' 不是 Number 对象
+15061,%s: 'this' 不是 VBArray 对象
+15062,%s: 'this' 不是 JavaScript 对象
+15063,%s: 'this' 不是 Enumerator 对象
+15064,%s: 'this' 不是 RegExp 对象
+15065,%s: 无效参数
+15066,%s: 参数不是 Object
+15067,%s: 参数不是 JavaScript 对象
+15068,%s: 参数不是 Function 对象
+15069,%s: 参数不是 VBArray 对象
+15070,%s: 参数为 null 或未定义
+15071,%s: 参数不是 Object 且不为 null
+15072,%s: 参数没有有效的“length”属性
+15073,%s: 缺少 Array 或 arguments 对象
+15074,“%s”的操作数无效: 缺少 Object
+15075,“%s”的操作数无效: 缺少 Function
+15076,属性“%s”的描述符无效
+15077,无法定义属性“%s”: 对象不可扩展
+15078,无法重新定义不可配置的属性“%s”
+15079,无法修改不可写的属性“%s”
+15080,无法修改属性“%s”:“length”不可写
+15081,无法定义属性“%s”
+15088,未指定 DataView 方法中的必需参数 %s
+15090,DataView 构造函数参数 %s 无效
+15091,函数“%s”具有无效的签名,无法调用该函数
+15092,属性“%s”具有无效的签名,无法访问该属性
+15093,不支持将 runtimeclass %s (默认接口为 Windows.Foundation.IPropertyValue)作为输入参数类型
+15094,不支持将接口为 Windows.Foundation.IPropertyValue (具有 runtimeclass 名称 %s)的对象作为输出参数
+15096,%s: "this" 不是 Inspectable 对象
+15097,%s: 无法将参数转换为 "char" 类型
+15098,%s: 无法将参数转换为 "GUID" 类型
+15099,%s: 无法将返回值转换为 IInspectable
+15100,无法将对象转换为结构: 对象缺少应有的属性 "%s"
+15101,找不到类型 "%s"
+15102,%s: 使用过少的参数调用函数
+15103,%s: 类型不可构造
+15104,无法将值转换为 PropertyValue: PropertyValue 不支持 %s
+15105,无法将值转换为 IInspectable: IInspectable 不支持 %s
+15108,%s: Inspectable 对象 "this" 已释放并且无法访问
+15110,'this' 不是所需的类型: %s
+15112,%s: 尝试获取元数据信息时发生意外故障
+32812,指定的日期不在当前区域设置的日历中
diff --git a/src/sentry/data/error-locale/zh-HK.txt b/src/sentry/data/error-locale/zh-HK.txt
new file mode 100644
index 00000000000000..16f9cc8527e579
--- /dev/null
+++ b/src/sentry/data/error-locale/zh-HK.txt
@@ -0,0 +1,289 @@
+5,程序呼叫或引數不正確
+6,溢位
+7,記憶體不足
+9,陣列索引超出範圍
+10,此陣列的長度是固定的或暫時被鎖住
+11,除以零
+13,類型不符
+14,字串空間不足
+17,無法執行所要求的動作
+28,堆疊空間不足
+35,沒有定義這個 Sub 或 Function
+48,載入 DLL 時發生錯誤
+51,內部錯誤
+52,錯誤的檔案名稱或代號
+53,找不到檔案
+54,錯誤的檔案模式
+55,檔案已開啟
+57,週邊設備 I/O 錯誤
+58,檔案已存在
+61,磁碟已滿
+62,輸入超出檔案結尾
+67,檔案過多
+68,無法使用周邊設備
+70,沒有使用權限
+71,磁碟尚未準備好
+74,在不同的磁碟機上,無法重新命名
+75,路徑或檔案存取錯誤
+76,找不到路徑
+91,沒有設定物件變數或 With 區塊變數
+92,沒有設定 For 迴圈的初始值
+94,Null 的使用不正確
+322,無法產生必要的暫時檔案
+424,此處需要物件
+429,Automation 伺服程式無法產生物件
+430,該物件類別不支援 Automation
+432,在 Automation 的運作過程中找不到檔案名稱或物件類別名稱
+438,物件不支援此屬性或方法
+440,Automation 錯誤
+445,物件不支援此動作
+446,物件不支援指名引數(Named Argument)
+447,物件不支援目前的地區設定
+448,找不到指名引數(Named Argument)
+449,引數不為選擇性
+450,引數的個數錯誤或指定了不正確的屬性
+451,此物件並非集合物件
+453,找不到指定的 DLL 函數
+458,此變數使用了 JavaScript 不支援的 Automation 型態
+462,遠端伺服器不存在或無法使用
+501,無法指定至變數
+502,物件無法安全地於 Scripting 中使用
+503,物件無法安全地初始化
+504,物件無法安全地建立
+507,發生例外情況
+1001,記憶體不足
+1002,語法錯誤
+1003,必須要有 ':'
+1004,必須要有 ';'
+1005,必須要有 '('
+1006,必須要有 ')'
+1007,必須要有 ']'
+1008,必須要有 '{'
+1009,必須要有 '}'
+1010,必須要有識別項
+1011,必須要有 '='
+1012,必須要有 '/'
+1013,不正確的數字
+1014,字元無效
+1015,無法判定字串常數的結尾
+1016,無法判定程式註解的結尾
+1018,不能在函數之外使用 'return' 陳述式
+1019,不能在迴圈之外使用 'break'
+1020,不能在迴圈之外使用 'continue'
+1023,必須是十六進位制的數值
+1024,必須要有 'while'
+1025,標籤被重新定義
+1026,標籤不存在
+1027,'switch' 陳述式中,'default' 僅能出現一次
+1028,必須要有識別項,字串或數字
+1029,必須要有 '@end'
+1030,關閉條件式編譯
+1031,必須要有常數
+1032,必須要有 '@'
+1033,必須要有 'catch'
+1034,必須要有 'var'
+1035,'throw' 必須接著一運算式,且於同一行程式碼
+1037,不可在 strict 模式中使用 'with' 陳述式
+1038,不可在 strict 模式中使用重複的型式參數名稱
+1039,不可在 strict 模式中使用八進位數值常值與逸出字元
+1041,在 strict 模式中使用 'eval' 無效
+1042,在 strict 模式中使用 'arguments' 無效
+1045,在 strict 模式中不可對運算式呼叫 delete
+1046,在 strict 模式中不可有屬性的多項定義
+1047,在 strict 模式中,無法在陳述式或區塊中使用巢狀函式宣告。它們只能出現在頂層或直接出現在函式主體內。
+1048,為識別碼使用關鍵字是無效的
+1049,為識別碼使用未來的保留字是無效的
+1050,為識別碼使用未來的保留字是無效的。識別碼名稱在 strict 模式中是保留字。
+1051,Setter 函式必須有一個引數
+4096,JavaScript 編譯錯誤
+4097,JavaScript 執行階段錯誤
+4098,未知的執行階段錯誤
+5000,無法指定至 'this'
+5001,必須要有數字
+5002,必須要有函數
+5003,無法指定為函數的結果
+5004,無法索引物件
+5005,必須要有字串
+5006,必須要有 Date 物件
+5007,必須要有物件
+5008,指派的左側無效
+5009,未定義的識別項
+5010,必須要有布林值
+5011,無法執行已被釋放的 Script
+5012,必須要有物件成員
+5013,必須要有 VBArray
+5014,必須要有 JavaScript 物件
+5015,必須要有 Enumerator 物件
+5016,必須要有通用運算式 (Regular Expression) 物件
+5017,在通用運算式 (Regular Expression) 中有語法錯誤
+5018,未預期的次數符號
+5019,通用(Regular) 運算式中,必須要有 ']'
+5020,通用(Regular) 運算式中,必須要有 ')'
+5021,不正確的字元集範圍
+5022,已丟出(Throw) 一 例外事件但並未被接收(Catch)
+5023,函數沒有一個正確的原型(Prototype) 物件
+5024,此 URI 含有一個不正確的字元
+5025,此 URI 不是個正確編碼
+5026,分數位的數值超出範圍
+5027,此精算值是超出範圍的
+5028,必須要有陣列或引數物件
+5029,陣列長度必須是一有限的正整數
+5030,陣列長度必須被指定為一有限的正值
+5031,需要有陣列或引數物件
+5034,不支援數值引數中的循環參照
+5035,無效的取代子引數
+5038,引數清單太大,無法套用
+5039,重新宣告 const 屬性
+5041,無法設定物件成員
+5042,未在 strict 模式中定義此變數
+5043,不可在 strict 模式中存取函式或引數物件的 'caller' 屬性
+5044,不可在 strict 模式中存取引數物件的 'callee' 屬性
+5045,不可在 strict 模式中指派唯讀屬性
+5046,不可建立無法延伸的物件
+5047,必須要有物件
+5048,必須要有物件
+5049,必須要有物件
+5050,必須要有物件
+5051,必須要有函數
+5052,必須要有函數
+5053,屬性不可同時具有存取子與值
+5054,'this' 為 null 或未經定義
+5055,必須要有物件
+5056,必須要有函數
+5057,必須要有字串
+5058,必須要有布林值
+5059,必須要有日期
+5060,必須要有數字
+5061,必須要有 VBArray
+5062,必須要有 JavaScript 物件
+5063,必須要有 Enumerator 物件
+5064,必須要有 RegExp 物件
+5065,無效的函數引數
+5066,必須要有物件
+5067,必須要有 JavaScript 物件
+5068,必須要有函數
+5069,必須要有 VBArray
+5070,必須要有物件
+5071,必須要有物件
+5072,無效的 'length' 屬性
+5073,必須要有陣列或引數物件
+5074,無效的運算元
+5075,無效的運算元
+5076,無效的屬性描述元
+5077,無法定義屬性: 物件不可延伸
+5078,無法重新定義不可設定的屬性
+5079,無法修改不可寫入的屬性
+5080,無法修改屬性: 'length' 不可寫入
+5081,無法定義屬性
+5082,已指定型別的陣列建構函式引數無效
+5083,'this' 不是已指定型別的陣列物件
+5084,建立已指定型別之陣列時指定的位移/長度無效
+5085,已指定型別之陣列子陣列方法中的開始/結束值無效
+5086,已指定型別之陣列集中的來源無效
+5087,'this' 不是 DataView 物件
+5088,DataView 中的引數無效
+5089,DataView 作業存取超過指定的緩衝區長度
+5090,DataView 中的引數無效
+5091,無效的函式簽章
+5092,無效的屬性簽章
+5093,無效的輸入參數型別
+5094,無效的輸出參數
+5095,不允許在 strict 模式中存取函式的 'arguments' 屬性
+5096,必須是可檢查的物件
+5097,無法將引數的類型轉換為 'char'
+5098,無法將引數的類型轉換為 'GUID'
+5099,必須是 IInspectable
+5100,無法將物件轉換為 struct: 物件缺少必要屬性
+5101,類型不詳
+5102,呼叫函式時使用的引數太多
+5103,類型不是可建構的
+5104,無法將值轉換為 PropertyValue: PropertyValue 不支援此類型
+5105,無法將值轉換為 IInspectable: IInspectable 不支援此類型
+5106,無法將 Date 轉換為 Windows.Foundation.DateTime: 值超過有效範圍
+5107,無法將值轉換為 Windows.Foundation.TimeSpan: 值超過有效範圍
+5108,對於已釋放之可檢查的物件的存取無效
+5109,無法釋放已釋放的可檢查的物件
+5110,'this' 不是預期的類型
+5111,為陣列指定的長度與大小不合法
+5112,嘗試取得中繼資料資訊時發生意外失敗
+5200,狀態是「錯誤」,但 getResults 未傳回錯誤
+5201,傳遞給已完成之處理常式的狀態參數遺失或無效
+5202,傳遞給已完成之處理常式的傳送者參數遺失或無效
+6000,無限
+6001,-無限
+10438,物件沒有支援這個屬性或方法 '%s'
+10449,函數 '%s' 的引數不為選擇性
+15001,'%s' 不是一個數字
+15002,'%s' 不是一個函數
+15004,'%s' 不是一個可給予索引的物件
+15005,'%s' 不是一個字串
+15006,'%s' 不是一個日期物件
+15007,'%s' 是 null 或不是一個物件
+15008,無法指定至 '%s'
+15009,'%s' 未經定義
+15010,'%s' 並非為布林值
+15012,無法刪除 '%s'
+15013,'%s' 不是一個 VBArray
+15014,'%s' 不是 JavaScript 物件
+15015,'%s' 不是一個列舉值物件
+15016,'%s' 不是一個通用運算式 (Regular Expression) 物件
+15028,%s 不是陣列或引數物件
+15031,%s 不是陣列物件
+15036,屬性描述元上的 '%s' 屬性在此物件上不可設為 'true'
+15037,屬性描述元上的 '%s' 屬性在此物件上不可設為 'false'
+15039,重新宣告常數 '%s'
+15041,不可在 strict 模式中對 '%s' 呼叫 delete
+15047,無法設定未定義或 Null 參考的屬性 '%s'
+15048,無法取得未定義或 Null 參考的屬性 '%s'
+15049,無法刪除未定義或 Null 參考的屬性 '%s'
+15050,無法存取屬性 '%s': 型別 'VarDate' 不支援使用者定義的屬性
+15051,屬性 '%s' 的值不是 Function 物件
+15052,屬性 '%s' 的值為 null 或未經定義,且不是 Function 物件
+15054,%s: 'this' 為 null 或未經定義
+15055,%s: 'this' 不是物件
+15056,%s: 'this' 不是 Function 物件
+15057,%s: 'this' 不是 String 物件
+15058,%s: 'this' 不是 Boolean 物件
+15059,%s: 'this' 不是 Date 物件
+15060,%s: 'this' 不是 Number 物件
+15061,%s: 'this' 不是 VBArray 物件
+15062,%s: 'this' 不是 JavaScript 物件
+15063,%s: 'this' 不是 Enumerator 物件
+15064,%s: 'this' 不是 RegExp 物件
+15065,%s: 無效的引數
+15066,%s: 引數不是物件
+15067,%s: 引數不是 JavaScript 物件
+15068,%s: 引數不是 Function 物件
+15069,%s: 引數不是 VBArray 物件
+15070,%s: 引數為 null 或未經定義
+15071,%s: 引數不是物件或不是 null
+15072,%s: 引數不具有效的 'length' 屬性
+15073,%s: 必須要有陣列或引數物件
+15074,'%s' 的運算元無效: 必須要有物件
+15075,'%s' 的運算元無效: 必須要有函數
+15076,屬性 '%s' 的描述子無效
+15077,無法定義屬性 '%s': 物件不可延伸
+15078,無法重新定義不可設定的屬性 '%s'
+15079,無法修改不可寫入的屬性 '%s'
+15080,無法修改屬性 '%s': 'length' 是不可寫入的
+15081,無法定義屬性 '%s'
+15088,未在 DataView 方法中指定必要引數 %s
+15090,DataView 建構函式引數 %s 無效
+15091,函式 '%s' 具有無效的簽章,因此無法呼叫
+15092,屬性 '%s' 具有無效的簽章,因此無法存取
+15093,不支援使用預設介面為 Windows.Foundation.IPropertyValue 的執行階段類別 %s 做為輸入參數型別
+15094,不支援使用介面為 Windows.Foundation.IPropertyValue (執行階段類別名稱為 %s) 的物件做為輸出參數
+15096,%s: 'this' 不是可檢查的物件
+15097,%s: 無法將引數的類型轉換為 'char'
+15098,%s: 無法將引數的類型轉換為 'GUID'
+15099,%s: 無法將傳回值轉換為 IInspectable
+15100,無法將物件轉換為 struct: 物件缺少必要屬性 '%s'
+15101,找不到類型 '%s'
+15102,%s: 呼叫函式時使用的引數太多
+15103,%s: 類型不是可建構的
+15104,無法將值轉換為 PropertyValue: PropertyValue 不支援 %s
+15105,無法將值轉換為 IInspectable: IInspectable 不支援 %s
+15108,%s: 可檢查的物件 'this' 已釋放,因此無法存取
+15110,'this' 不是預期的類型: %s
+15112,%s: 嘗試取得中繼資料資訊時發生意外失敗
+32812,指定日期無法在目前地區設定的日曆樣式使用
diff --git a/src/sentry/data/samples/cocoa.json b/src/sentry/data/samples/cocoa.json
index 8c52b8868be243..f319b8f2e37374 100644
--- a/src/sentry/data/samples/cocoa.json
+++ b/src/sentry/data/samples/cocoa.json
@@ -1267,7 +1267,7 @@
"usable_memory": 2095349760
}
},
- "sdk": {"name": "sentry-cocoa", "version": "3.3.3"},
+ "sdk": {"name": "sentry-cocoa", "version": "3.11.1"},
"debug_meta": {
"images": [
{
diff --git a/src/sentry/db/models/base.py b/src/sentry/db/models/base.py
index 5db61cc6016d93..39f0b776558249 100644
--- a/src/sentry/db/models/base.py
+++ b/src/sentry/db/models/base.py
@@ -8,9 +8,11 @@
from __future__ import absolute_import
+from copy import copy
import logging
import six
+from bitfield.types import BitHandler
from django.db import models
from django.db.models import signals
@@ -74,10 +76,14 @@ def _update_tracked_data(self):
data = {}
for f in self._meta.fields:
try:
- data[f.column] = self.__get_field_value(f)
+ v = self.__get_field_value(f)
except AttributeError as e:
# this case can come up from pickling
logging.exception(six.text_type(e))
+ else:
+ if isinstance(v, BitHandler):
+ v = copy(v)
+ data[f.column] = v
self.__data = data
else:
self.__data = UNSAVED
diff --git a/src/sentry/db/postgres/base.py b/src/sentry/db/postgres/base.py
index ca240c456ab16c..c4928237bbff63 100644
--- a/src/sentry/db/postgres/base.py
+++ b/src/sentry/db/postgres/base.py
@@ -1,5 +1,6 @@
from __future__ import absolute_import
+from six import string_types
import psycopg2 as Database
# Some of these imports are unused, but they are inherited from other engines
@@ -15,6 +16,12 @@
__all__ = ('DatabaseWrapper', )
+def remove_null(value):
+ if not isinstance(value, string_types):
+ return value
+ return value.replace('\x00', '')
+
+
class CursorWrapper(object):
"""
A wrapper around the postgresql_psycopg2 backend which handles various events
@@ -36,7 +43,24 @@ def __iter__(self):
@less_shitty_error_messages
def execute(self, sql, params=None):
if params is not None:
- return self.cursor.execute(sql, params)
+ try:
+ return self.cursor.execute(sql, params)
+ except ValueError as e:
+ # In psycopg2 2.7+, behavior was introduced where a
+ # NULL byte in a parameter would start raising a ValueError.
+ # psycopg2 chose to do this rather than let Postgres silently
+ # truncate the data, which is it's behavior when it sees a
+ # NULL byte. But for us, we'd rather remove the null value so it's
+ # somewhat legible rather than error. Considering this is better
+ # behavior than the database truncating, seems good to do this
+ # rather than attempting to sanitize all data inputs now manually.
+
+ # Note: This message is brittle, but it's currently hardcoded into
+ # psycopg2 for this behavior. If anything changes, we're choosing to
+ # address that later rather than potentially catch incorrect behavior.
+ if e.message != 'A string literal cannot contain NUL (0x00) characters.':
+ raise
+ return self.cursor.execute(sql, [remove_null(param) for param in params])
return self.cursor.execute(sql)
@capture_transaction_exceptions
diff --git a/src/sentry/debug/middleware.py b/src/sentry/debug/middleware.py
index 1ab1d106e70f2e..1b892dcf488d08 100644
--- a/src/sentry/debug/middleware.py
+++ b/src/sentry/debug/middleware.py
@@ -9,6 +9,9 @@
from django.utils.html import escape
from six.moves import _thread as thread
+from sentry.auth.superuser import is_active_superuser
+
+
WRAPPER = """
@@ -42,9 +45,14 @@ class DebugMiddleware(object):
_body_regexp = re.compile(re.escape(''), flags=re.IGNORECASE)
def show_toolbar_for_request(self, request):
+ # This avoids touching user session, which means we avoid
+ # setting `Vary: Cookie` as a response header which will
+ # break HTTP caching entirely.
+ if request.path_info.startswith(settings.ANONYMOUS_STATIC_PREFIXES):
+ return
if not settings.SENTRY_DEBUGGER:
return False
- if not request.is_superuser():
+ if not is_active_superuser(request):
return False
if 'text/html' not in request.META.get('HTTP_ACCEPT', '*/*'):
return False
diff --git a/src/sentry/deletions/__init__.py b/src/sentry/deletions/__init__.py
index 388bcd9cd5b2a0..1e75205e817237 100644
--- a/src/sentry/deletions/__init__.py
+++ b/src/sentry/deletions/__init__.py
@@ -43,34 +43,33 @@ def load_defaults():
default_manager.register(models.EnvironmentProject, BulkModelDeletionTask)
default_manager.register(models.Event, defaults.EventDeletionTask)
default_manager.register(models.EventMapping, BulkModelDeletionTask)
- default_manager.register(models.EventTag, BulkModelDeletionTask)
default_manager.register(models.EventUser, BulkModelDeletionTask)
default_manager.register(models.Group, defaults.GroupDeletionTask)
default_manager.register(models.GroupAssignee, BulkModelDeletionTask)
default_manager.register(models.GroupBookmark, BulkModelDeletionTask)
default_manager.register(models.GroupCommitResolution, BulkModelDeletionTask)
default_manager.register(models.GroupEmailThread, BulkModelDeletionTask)
+ default_manager.register(models.GroupEnvironment, BulkModelDeletionTask)
default_manager.register(models.GroupHash, BulkModelDeletionTask)
+ default_manager.register(models.GroupLink, BulkModelDeletionTask)
default_manager.register(models.GroupMeta, BulkModelDeletionTask)
default_manager.register(models.GroupRedirect, BulkModelDeletionTask)
default_manager.register(models.GroupRelease, BulkModelDeletionTask)
default_manager.register(models.GroupResolution, BulkModelDeletionTask)
default_manager.register(models.GroupRuleStatus, BulkModelDeletionTask)
default_manager.register(models.GroupSeen, BulkModelDeletionTask)
+ default_manager.register(models.GroupShare, BulkModelDeletionTask)
default_manager.register(models.GroupSnooze, BulkModelDeletionTask)
default_manager.register(models.GroupSubscription, BulkModelDeletionTask)
- default_manager.register(models.GroupTagKey, BulkModelDeletionTask)
- default_manager.register(models.GroupTagValue, BulkModelDeletionTask)
default_manager.register(models.Organization, defaults.OrganizationDeletionTask)
default_manager.register(models.OrganizationMemberTeam, BulkModelDeletionTask)
default_manager.register(models.Project, defaults.ProjectDeletionTask)
default_manager.register(models.ProjectBookmark, BulkModelDeletionTask)
default_manager.register(models.ProjectKey, BulkModelDeletionTask)
+ default_manager.register(models.PullRequest, BulkModelDeletionTask)
default_manager.register(models.Repository, defaults.RepositoryDeletionTask)
default_manager.register(models.SavedSearch, BulkModelDeletionTask)
default_manager.register(models.SavedSearchUserDefault, BulkModelDeletionTask)
- default_manager.register(models.TagKey, defaults.TagKeyDeletionTask)
- default_manager.register(models.TagValue, BulkModelDeletionTask)
default_manager.register(models.Team, defaults.TeamDeletionTask)
default_manager.register(models.UserReport, BulkModelDeletionTask)
diff --git a/src/sentry/deletions/base.py b/src/sentry/deletions/base.py
index 6d501239843279..e4559d4c08ffa9 100644
--- a/src/sentry/deletions/base.py
+++ b/src/sentry/deletions/base.py
@@ -1,10 +1,13 @@
from __future__ import absolute_import, print_function
import logging
+import re
from sentry.constants import ObjectStatus
from sentry.utils.query import bulk_delete_objects
+_leaf_re = re.compile(r'^(Event|Group)(.+)')
+
class BaseRelation(object):
def __init__(self, params, task):
@@ -60,6 +63,12 @@ def get_child_relations_bulk(self, instance_list):
# ModelRelation(Model, {'parent_id__in': [i.id for id in instance_list]})
]
+ def extend_relations(self, child_relations, obj):
+ return child_relations
+
+ def extend_relations_bulk(self, child_relations, obj_list):
+ return child_relations
+
def filter_relations(self, child_relations):
if not self.skip_models or not child_relations:
return child_relations
@@ -77,6 +86,7 @@ def delete_bulk(self, instance_list):
self.mark_deletion_in_progress(instance_list)
child_relations = self.get_child_relations_bulk(instance_list)
+ child_relations = self.extend_relations_bulk(child_relations, instance_list)
child_relations = self.filter_relations(child_relations)
if child_relations:
has_more = self.delete_children(child_relations)
@@ -85,6 +95,7 @@ def delete_bulk(self, instance_list):
for instance in instance_list:
child_relations = self.get_child_relations(instance)
+ child_relations = self.extend_relations(child_relations, instance)
child_relations = self.filter_relations(child_relations)
if child_relations:
has_more = self.delete_children(child_relations)
@@ -135,6 +146,17 @@ def __repr__(self):
type(self), self.model, self.query, self.order_by, self.transaction_id, self.actor_id,
)
+ def extend_relations(self, child_relations, obj):
+ from sentry.deletions import default_manager
+
+ return child_relations + [rel(obj) for rel in default_manager.dependencies[self.model]]
+
+ def extend_relations_bulk(self, child_relations, obj_list):
+ from sentry.deletions import default_manager
+
+ return child_relations + [rel(obj_list)
+ for rel in default_manager.bulk_dependencies[self.model]]
+
def chunk(self, num_shards=None, shard_id=None):
"""
Deletes a chunk of this instance's data. Return ``True`` if there is
@@ -177,15 +199,18 @@ def delete_instance(self, instance):
try:
instance.delete()
finally:
- self.logger.info(
- 'object.delete.executed',
- extra={
- 'object_id': instance_id,
- 'transaction_id': self.transaction_id,
- 'app_label': instance._meta.app_label,
- 'model': type(instance).__name__,
- }
- )
+ # Don't log Group and Event child object deletions.
+ model_name = type(instance).__name__
+ if not _leaf_re.search(model_name):
+ self.logger.info(
+ 'object.delete.executed',
+ extra={
+ 'object_id': instance_id,
+ 'transaction_id': self.transaction_id,
+ 'app_label': instance._meta.app_label,
+ 'model': model_name,
+ }
+ )
def get_actor(self):
from sentry.models import User
@@ -225,13 +250,16 @@ def delete_instance_bulk(self):
**self.query
)
finally:
- self.logger.info(
- 'object.delete.bulk_executed',
- extra=dict(
- {
- 'transaction_id': self.transaction_id,
- 'app_label': self.model._meta.app_label,
- 'model': self.model.__name__,
- }, **self.query
+ # Don't log Group and Event child object deletions.
+ model_name = self.model.__name__
+ if not _leaf_re.search(model_name):
+ self.logger.info(
+ 'object.delete.bulk_executed',
+ extra=dict(
+ {
+ 'transaction_id': self.transaction_id,
+ 'app_label': self.model._meta.app_label,
+ 'model': model_name,
+ }, **self.query
+ )
)
- )
diff --git a/src/sentry/deletions/defaults/event.py b/src/sentry/deletions/defaults/event.py
index 8e01c6c4eb9a2d..33fa94f56955cf 100644
--- a/src/sentry/deletions/defaults/event.py
+++ b/src/sentry/deletions/defaults/event.py
@@ -2,7 +2,7 @@
from sentry import nodestore
-from ..base import (BaseDeletionTask, BaseRelation, ModelDeletionTask, ModelRelation)
+from ..base import (BaseDeletionTask, BaseRelation, ModelDeletionTask)
class NodeDeletionTask(BaseDeletionTask):
@@ -17,16 +17,6 @@ def chunk(self):
class EventDeletionTask(ModelDeletionTask):
def get_child_relations_bulk(self, instance_list):
- from sentry.models import EventTag
-
node_ids = [i.data.id for i in instance_list]
- event_ids = [i.id for i in instance_list]
-
- return [
- BaseRelation({
- 'nodes': node_ids
- }, NodeDeletionTask),
- ModelRelation(EventTag, {
- 'event_id__in': event_ids,
- }, ModelDeletionTask),
- ]
+
+ return [BaseRelation({'nodes': node_ids}, NodeDeletionTask)]
diff --git a/src/sentry/deletions/defaults/group.py b/src/sentry/deletions/defaults/group.py
index 34ef7029d05112..52cd554a57159e 100644
--- a/src/sentry/deletions/defaults/group.py
+++ b/src/sentry/deletions/defaults/group.py
@@ -12,25 +12,27 @@ def get_child_relations(self, instance):
model_list = (
# prioritize GroupHash
models.GroupHash,
- models.EventTag,
models.EventMapping,
models.GroupAssignee,
models.GroupCommitResolution,
+ models.GroupLink,
models.GroupBookmark,
models.GroupMeta,
+ models.GroupEnvironment,
models.GroupRelease,
models.GroupRedirect,
models.GroupResolution,
models.GroupRuleStatus,
+ models.GroupSeen,
+ models.GroupShare,
models.GroupSnooze,
- models.GroupTagValue,
- models.GroupTagKey,
models.GroupEmailThread,
models.GroupSubscription,
models.UserReport,
# Event is last as its the most time consuming
models.Event,
)
+
relations.extend([ModelRelation(m, {'group_id': instance.id}) for m in model_list])
return relations
diff --git a/src/sentry/deletions/defaults/project.py b/src/sentry/deletions/defaults/project.py
index 2b9cc043117440..8fa4a8f10bd920 100644
--- a/src/sentry/deletions/defaults/project.py
+++ b/src/sentry/deletions/defaults/project.py
@@ -15,12 +15,12 @@ def get_child_relations(self, instance):
# in bulk
model_list = (
models.Activity, models.EnvironmentProject, models.EventMapping, models.EventUser,
- models.EventTag, models.GroupAssignee, models.GroupBookmark, models.GroupEmailThread,
+ models.GroupAssignee, models.GroupBookmark, models.GroupEmailThread,
models.GroupHash, models.GroupRelease, models.GroupRuleStatus, models.GroupSeen,
- models.GroupSubscription, models.GroupTagKey, models.GroupTagValue,
- models.ProjectBookmark, models.ProjectKey, models.SavedSearchUserDefault,
- models.SavedSearch, models.TagKey, models.TagValue, models.UserReport,
+ models.GroupShare, models.GroupSubscription, models.ProjectBookmark, models.ProjectKey,
+ models.SavedSearchUserDefault, models.SavedSearch, models.ServiceHook, models.UserReport,
)
+
relations.extend(
[
ModelRelation(m, {'project_id': instance.id}, BulkModelDeletionTask)
@@ -42,7 +42,8 @@ def get_child_relations(self, instance):
# in bulk
# Release needs to handle deletes after Group is cleaned up as the foreign
# key is protected
- model_list = (models.Group, models.ReleaseProject, models.ProjectDSymFile)
+ model_list = (models.Group, models.ReleaseProject, models.ProjectDSymFile,
+ models.ProjectSymCacheFile)
relations.extend(
[ModelRelation(m, {'project_id': instance.id}, ModelDeletionTask) for m in model_list]
)
diff --git a/src/sentry/deletions/defaults/tagkey.py b/src/sentry/deletions/defaults/tagkey.py
deleted file mode 100644
index 8a5390a8f464b0..00000000000000
--- a/src/sentry/deletions/defaults/tagkey.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from __future__ import absolute_import, print_function
-
-from ..base import ModelDeletionTask, ModelRelation
-
-
-class TagKeyDeletionTask(ModelDeletionTask):
- def get_child_relations(self, instance):
- from sentry.models import (EventTag, GroupTagKey, GroupTagValue, TagValue)
-
- # in bulk
- model_list = (GroupTagValue, GroupTagKey, TagValue)
- relations = [
- ModelRelation(m, {
- 'project_id': instance.project_id,
- 'key': instance.key,
- }) for m in model_list
- ]
- relations.append(
- ModelRelation(EventTag, {
- 'project_id': instance.project_id,
- 'key_id': instance.id,
- })
- )
- return relations
-
- def mark_deletion_in_progress(self, instance_list):
- from sentry.tagstore import TagKeyStatus
-
- for instance in instance_list:
- if instance.status != TagKeyStatus.DELETION_IN_PROGRESS:
- instance.update(status=TagKeyStatus.DELETION_IN_PROGRESS)
diff --git a/src/sentry/deletions/manager.py b/src/sentry/deletions/manager.py
index e4a5073a732d26..983625266b33bc 100644
--- a/src/sentry/deletions/manager.py
+++ b/src/sentry/deletions/manager.py
@@ -1,5 +1,7 @@
from __future__ import absolute_import, print_function
+from collections import defaultdict
+
__all__ = ['DeletionTaskManager']
@@ -7,6 +9,8 @@ class DeletionTaskManager(object):
def __init__(self, default_task=None):
self.tasks = {}
self.default_task = default_task
+ self.dependencies = defaultdict(set)
+ self.bulk_dependencies = defaultdict(set)
def get(self, task=None, **kwargs):
if task is None:
@@ -19,3 +23,9 @@ def get(self, task=None, **kwargs):
def register(self, model, task):
self.tasks[model] = task
+
+ def add_dependencies(self, model, dependencies):
+ self.dependencies[model] |= set(dependencies)
+
+ def add_bulk_dependencies(self, model, dependencies):
+ self.bulk_dependencies[model] |= set(dependencies)
diff --git a/src/sentry/digests/backends/redis.py b/src/sentry/digests/backends/redis.py
index 47cc9d8fa259e9..fad2cfe3beebb2 100644
--- a/src/sentry/digests/backends/redis.py
+++ b/src/sentry/digests/backends/redis.py
@@ -228,10 +228,10 @@ def digest(self, key, minimum_delay=None, timestamp=None):
raise
records = map(
- lambda (key, value, timestamp): Record(
- key,
- self.codec.decode(value) if value is not None else None,
- float(timestamp),
+ lambda key__value__timestamp: Record(
+ key__value__timestamp[0],
+ self.codec.decode(key__value__timestamp[1]) if key__value__timestamp[1] is not None else None,
+ float(key__value__timestamp[2]),
),
response,
)
diff --git a/src/sentry/django_admin.py b/src/sentry/django_admin.py
index 89e78de8d7d8cb..4dfd1bd96f458a 100644
--- a/src/sentry/django_admin.py
+++ b/src/sentry/django_admin.py
@@ -3,10 +3,12 @@
from copy import copy
from django.contrib import admin
+from sentry.auth.superuser import is_active_superuser
+
class RestrictiveAdminSite(admin.AdminSite):
def has_permission(self, request):
- return request.is_superuser()
+ return is_active_superuser(request)
def make_site():
diff --git a/src/sentry/event_manager.py b/src/sentry/event_manager.py
index adeb7497bd4703..69b4bd44be9a07 100644
--- a/src/sentry/event_manager.py
+++ b/src/sentry/event_manager.py
@@ -20,30 +20,36 @@
from hashlib import md5
from uuid import uuid4
-from sentry import eventtypes, features, buffer, tagstore
+from sentry import eventtypes, features, buffer
# we need a bunch of unexposed functions from tsdb
from sentry.tsdb import backend as tsdb
from sentry.constants import (
- CLIENT_RESERVED_ATTRS, LOG_LEVELS, DEFAULT_LOGGER_NAME, MAX_CULPRIT_LENGTH
+ CLIENT_RESERVED_ATTRS, LOG_LEVELS, LOG_LEVELS_MAP, DEFAULT_LOG_LEVEL,
+ DEFAULT_LOGGER_NAME, MAX_CULPRIT_LENGTH, VALID_PLATFORMS
)
-from sentry.interfaces.base import get_interface
+from sentry.interfaces.base import get_interface, InterfaceValidationError
+from sentry.interfaces.schemas import validate_and_default_interface
from sentry.models import (
- Activity, Environment, Event, EventMapping, EventUser, Group, GroupHash, GroupRelease,
- GroupResolution, GroupStatus, Project, Release, ReleaseEnvironment, ReleaseProject,
- UserReport
+ Activity, Environment, Event, EventError, EventMapping, EventUser, Group,
+ GroupEnvironment, GroupHash, GroupRelease, GroupResolution, GroupStatus,
+ Project, Release, ReleaseEnvironment, ReleaseProject, UserReport
)
from sentry.plugins import plugins
-from sentry.signals import first_event_received, regression_signal
+from sentry.signals import event_discarded, event_saved, first_event_received, regression_signal
from sentry.tasks.merge import merge_group
from sentry.tasks.post_process import post_process_group
+from sentry.utils import metrics
from sentry.utils.cache import default_cache
from sentry.utils.db import get_db_engine
-from sentry.utils.safe import safe_execute, trim, trim_dict
+from sentry.utils.safe import safe_execute, trim, trim_dict, get_path
from sentry.utils.strings import truncatechars
-from sentry.utils.validators import validate_ip
+from sentry.utils.validators import is_float
from sentry.stacktraces import normalize_in_app
+DEFAULT_FINGERPRINT_VALUES = frozenset(['{{ default }}', '{{default}}'])
+
+
def count_limit(count):
# TODO: could we do something like num_to_store = max(math.sqrt(100*count)+59, 200) ?
# ~ 150 * ((log(n) - 1.5) ^ 2 - 0.25)
@@ -87,7 +93,8 @@ def get_hashes_for_event_with_reason(event):
if not result:
continue
return (interface.get_path(), result)
- return ('message', [event.message])
+
+ return ('no_interfaces', [''])
def get_grouping_behavior(event):
@@ -99,8 +106,7 @@ def get_grouping_behavior(event):
def get_hashes_from_fingerprint(event, fingerprint):
- default_values = set(['{{ default }}', '{{default}}'])
- if any(d in fingerprint for d in default_values):
+ if any(d in fingerprint for d in DEFAULT_FINGERPRINT_VALUES):
default_hashes = get_hashes_for_event(event)
hash_count = len(default_hashes)
else:
@@ -110,7 +116,7 @@ def get_hashes_from_fingerprint(event, fingerprint):
for idx in range(hash_count):
result = []
for bit in fingerprint:
- if bit in default_values:
+ if bit in DEFAULT_FINGERPRINT_VALUES:
result.extend(default_hashes[idx])
else:
result.append(bit)
@@ -119,8 +125,7 @@ def get_hashes_from_fingerprint(event, fingerprint):
def get_hashes_from_fingerprint_with_reason(event, fingerprint):
- default_values = set(['{{ default }}', '{{default}}'])
- if any(d in fingerprint for d in default_values):
+ if any(d in fingerprint for d in DEFAULT_FINGERPRINT_VALUES):
default_hashes = get_hashes_for_event_with_reason(event)
hash_count = len(default_hashes[1])
else:
@@ -129,7 +134,7 @@ def get_hashes_from_fingerprint_with_reason(event, fingerprint):
hashes = OrderedDict((bit, []) for bit in fingerprint)
for idx in range(hash_count):
for bit in fingerprint:
- if bit in default_values:
+ if bit in DEFAULT_FINGERPRINT_VALUES:
hashes[bit].append(default_hashes)
else:
hashes[bit] = bit
@@ -192,6 +197,45 @@ def plugin_is_regression(group, event):
return True
+def process_timestamp(value, current_datetime=None):
+ if is_float(value):
+ try:
+ value = datetime.fromtimestamp(float(value))
+ except Exception:
+ raise InvalidTimestamp(
+ 'Invalid value for timestamp: %r' % value)
+ elif not isinstance(value, datetime):
+ # all timestamps are in UTC, but the marker is optional
+ if value.endswith('Z'):
+ value = value[:-1]
+ if '.' in value:
+ # Python doesn't support long microsecond values
+ # https://github.com/getsentry/sentry/issues/1610
+ ts_bits = value.split('.', 1)
+ value = '%s.%s' % (ts_bits[0], ts_bits[1][:2])
+ fmt = '%Y-%m-%dT%H:%M:%S.%f'
+ else:
+ fmt = '%Y-%m-%dT%H:%M:%S'
+ try:
+ value = datetime.strptime(value, fmt)
+ except Exception:
+ raise InvalidTimestamp(
+ 'Invalid value for timestamp: %r' % value)
+
+ if current_datetime is None:
+ current_datetime = datetime.now()
+
+ if value > current_datetime + timedelta(minutes=1):
+ raise InvalidTimestamp(
+ 'Invalid value for timestamp (in future): %r' % value)
+
+ if value < current_datetime - timedelta(days=30):
+ raise InvalidTimestamp(
+ 'Invalid value for timestamp (too old): %r' % value)
+
+ return float(value.strftime('%s'))
+
+
class HashDiscarded(Exception):
pass
@@ -228,6 +272,10 @@ def calculate(cls, times_seen, last_seen):
return math.log(times_seen) * 600 + float(last_seen.strftime('%s'))
+class InvalidTimestamp(Exception):
+ pass
+
+
class EventManager(object):
logger = logging.getLogger('sentry.events')
@@ -235,33 +283,123 @@ def __init__(self, data, version='5'):
self.data = data
self.version = version
- def normalize(self):
- # TODO(dcramer): store http.env.REMOTE_ADDR as user.ip
- # First we pull out our top-level (non-data attr) kwargs
+ def normalize(self, request_env=None):
+ request_env = request_env or {}
data = self.data
+ errors = data['errors'] = []
+
+ # Before validating with a schema, attempt to cast values to their desired types
+ # so that the schema doesn't have to take every type variation into account.
+ text = six.text_type
+ fp_types = six.string_types + six.integer_types + (float, )
+
+ def to_values(v):
+ return {'values': v} if v and isinstance(v, (tuple, list)) else v
+
+ casts = {
+ 'environment': lambda v: text(v) if v is not None else v,
+ 'fingerprint': lambda v: list(map(text, v)) if isinstance(v, list) and all(isinstance(f, fp_types) for f in v) else v,
+ 'release': lambda v: text(v) if v is not None else v,
+ 'dist': lambda v: text(v).strip() if v is not None else v,
+ 'time_spent': lambda v: int(v) if v is not None else v,
+ 'tags': lambda v: [(text(v_k).replace(' ', '-').strip(), text(v_v).strip()) for (v_k, v_v) in dict(v).items()],
+ 'timestamp': lambda v: process_timestamp(v),
+ 'platform': lambda v: v if v in VALID_PLATFORMS else 'other',
+ 'sentry.interfaces.Message': lambda v: v if isinstance(v, dict) else {'message': v},
+
+ # These can be sent as lists and need to be converted to {'values': [...]}
+ 'exception': to_values,
+ 'sentry.interfaces.Exception': to_values,
+ 'breadcrumbs': to_values,
+ 'sentry.interfaces.Breadcrumbs': to_values,
+ 'threads': to_values,
+ 'sentry.interfaces.Threads': to_values,
+ }
+
+ for c in casts:
+ if c in data:
+ try:
+ data[c] = casts[c](data[c])
+ except Exception as e:
+ errors.append({'type': EventError.INVALID_DATA, 'name': c, 'value': data[c]})
+ del data[c]
+
+ # raw 'message' is coerced to the Message interface, as its used for pure index of
+ # searchable strings. If both a raw 'message' and a Message interface exist, try and
+ # add the former as the 'formatted' attribute of the latter.
+ # See GH-3248
+ msg_str = data.pop('message', None)
+ if msg_str:
+ msg_if = data.setdefault('sentry.interfaces.Message', {'message': msg_str})
+ if msg_if.get('message') != msg_str:
+ msg_if.setdefault('formatted', msg_str)
+
+ # Fill in ip addresses marked as {{auto}}
+ client_ip = request_env.get('client_ip')
+ if client_ip:
+ if get_path(data, ['sentry.interfaces.Http', 'env', 'REMOTE_ADDR']) == '{{auto}}':
+ data['sentry.interfaces.Http']['env']['REMOTE_ADDR'] = client_ip
+
+ if get_path(data, ['request', 'env', 'REMOTE_ADDR']) == '{{auto}}':
+ data['request']['env']['REMOTE_ADDR'] = client_ip
+
+ if get_path(data, ['sentry.interfaces.User', 'ip_address']) == '{{auto}}':
+ data['sentry.interfaces.User']['ip_address'] = client_ip
+
+ if get_path(data, ['user', 'ip_address']) == '{{auto}}':
+ data['user']['ip_address'] = client_ip
+
+ # Validate main event body and tags against schema
+ is_valid, event_errors = validate_and_default_interface(data, 'event')
+ errors.extend(event_errors)
+ if 'tags' in data:
+ is_valid, tag_errors = validate_and_default_interface(data['tags'], 'tags', name='tags')
+ errors.extend(tag_errors)
+
+ # Validate interfaces
+ for k in list(iter(data)):
+ if k in CLIENT_RESERVED_ATTRS:
+ continue
- if not isinstance(data.get('level'), (six.string_types, int)):
- data['level'] = logging.ERROR
- elif data['level'] not in LOG_LEVELS:
- data['level'] = logging.ERROR
+ value = data.pop(k)
- if not data.get('logger') or not isinstance(data.get('logger'), six.string_types):
- data['logger'] = DEFAULT_LOGGER_NAME
- else:
- logger = trim(data['logger'].strip(), 64)
- if tagstore.is_valid_key(logger):
- data['logger'] = logger
- else:
- data['logger'] = DEFAULT_LOGGER_NAME
+ if not value:
+ self.logger.debug('Ignored empty interface value: %s', k)
+ continue
+
+ try:
+ interface = get_interface(k)
+ except ValueError:
+ self.logger.debug('Ignored unknown attribute: %s', k)
+ errors.append({'type': EventError.INVALID_ATTRIBUTE, 'name': k})
+ continue
+
+ try:
+ inst = interface.to_python(value)
+ data[inst.get_path()] = inst.to_json()
+ except Exception as e:
+ log = self.logger.debug if isinstance(
+ e, InterfaceValidationError) else self.logger.error
+ log('Discarded invalid value for interface: %s (%r)', k, value, exc_info=True)
+ errors.append({'type': EventError.INVALID_DATA, 'name': k, 'value': value})
- if data.get('platform'):
- data['platform'] = trim(data['platform'], 64)
+ # Additional data coercion and defaulting
+ level = data.get('level') or DEFAULT_LOG_LEVEL
+ if isinstance(level, int) or (isinstance(level, six.string_types) and level.isdigit()):
+ level = LOG_LEVELS.get(int(level), DEFAULT_LOG_LEVEL)
+ data['level'] = LOG_LEVELS_MAP.get(level, LOG_LEVELS_MAP[DEFAULT_LOG_LEVEL])
+
+ if data.get('dist') and not data.get('release'):
+ data['dist'] = None
- current_timestamp = timezone.now()
timestamp = data.get('timestamp')
if not timestamp:
- timestamp = current_timestamp
+ timestamp = timezone.now()
+ # TODO (alex) can this all be replaced by utcnow?
+ # it looks like the only time that this would even be hit is when timestamp
+ # is not defined, as the earlier process_timestamp already converts existing
+ # timestamps to floats.
if isinstance(timestamp, datetime):
# We must convert date to local time so Django doesn't mess it up
# based on TIME_ZONE
@@ -275,129 +413,65 @@ def normalize(self):
data['timestamp'] = timestamp
data['received'] = float(timezone.now().strftime('%s'))
- if not data.get('event_id'):
- data['event_id'] = uuid4().hex
-
- data.setdefault('culprit', None)
- data.setdefault('server_name', None)
- data.setdefault('site', None)
data.setdefault('checksum', None)
- data.setdefault('fingerprint', None)
- data.setdefault('platform', None)
+ data.setdefault('culprit', None)
data.setdefault('dist', None)
data.setdefault('environment', None)
data.setdefault('extra', {})
- data.setdefault('errors', [])
-
- tags = data.get('tags')
- if not tags:
- tags = []
- # full support for dict syntax
- elif isinstance(tags, dict):
- tags = list(tags.items())
- # prevent [tag, tag, tag] (invalid) syntax
- elif not all(len(t) == 2 for t in tags):
- tags = []
- else:
- tags = list(tags)
-
- data['tags'] = []
- for key, value in tags:
- key = six.text_type(key).strip()
- value = six.text_type(value).strip()
- if not (key and value):
- continue
-
- # XXX(dcramer): many legacy apps are using the environment tag
- # rather than the key itself
- if key == 'environment' and not data.get('environment'):
- data['environment'] = value
- else:
- data['tags'].append((key, value))
-
- if not isinstance(data['extra'], dict):
- # throw it away
- data['extra'] = {}
-
- trim_dict(data['extra'], max_size=settings.SENTRY_MAX_EXTRA_VARIABLE_SIZE)
-
- # TODO(dcramer): more of validate data needs stuffed into the manager
- for key in list(iter(data)):
- if key in CLIENT_RESERVED_ATTRS:
- continue
-
- value = data.pop(key)
-
- try:
- interface = get_interface(key)()
- except ValueError:
- continue
-
- try:
- inst = interface.to_python(value)
- data[inst.get_path()] = inst.to_json()
- except Exception:
- # XXX: we should consider logging this.
- pass
-
- # TODO(dcramer): this logic is duplicated in ``validate_data`` from
- # coreapi
-
- # message is coerced to an interface, as its used for pure
- # index of searchable strings
- # See GH-3248
- message = data.pop('message', None)
- if message:
- if 'sentry.interfaces.Message' not in data:
- interface = get_interface('sentry.interfaces.Message')
- try:
- inst = interface.to_python({
- 'message': message,
- })
- data[inst.get_path()] = inst.to_json()
- except Exception:
- pass
- elif not data['sentry.interfaces.Message'].get('formatted'):
- interface = get_interface('sentry.interfaces.Message')
- try:
- inst = interface.to_python(
- dict(
- data['sentry.interfaces.Message'],
- formatted=message,
- )
- )
- data[inst.get_path()] = inst.to_json()
- except Exception:
- pass
+ data.setdefault('fingerprint', None)
+ data.setdefault('logger', DEFAULT_LOGGER_NAME)
+ data.setdefault('platform', None)
+ data.setdefault('server_name', None)
+ data.setdefault('site', None)
+ data.setdefault('tags', [])
+ data.setdefault('transaction', None)
+
+ # Fix case where legacy apps pass 'environment' as a tag
+ # instead of a top level key.
+ # TODO (alex) save() just reinserts the environment into the tags
+ if not data.get('environment'):
+ tagsdict = dict(data['tags'])
+ if 'environment' in tagsdict:
+ data['environment'] = tagsdict['environment']
+ del tagsdict['environment']
+ data['tags'] = tagsdict.items()
# the SDKs currently do not describe event types, and we must infer
# them from available attributes
data['type'] = eventtypes.infer(data).key
-
data['version'] = self.version
- # TODO(dcramer): find a better place for this logic
exception = data.get('sentry.interfaces.Exception')
stacktrace = data.get('sentry.interfaces.Stacktrace')
if exception and len(exception['values']) == 1 and stacktrace:
exception['values'][0]['stacktrace'] = stacktrace
del data['sentry.interfaces.Stacktrace']
- if 'sentry.interfaces.Http' in data:
- try:
- ip_address = validate_ip(
- data['sentry.interfaces.Http'].get('env', {}).get('REMOTE_ADDR'),
- required=False,
- )
- except ValueError:
- ip_address = None
- if ip_address:
- data.setdefault('sentry.interfaces.User', {})
- data['sentry.interfaces.User'].setdefault('ip_address', ip_address)
+ # If there is no User ip_addres, update it either from the Http interface
+ # or the client_ip of the request.
+ auth = request_env.get('auth')
+ is_public = auth and auth.is_public
+ add_ip_platforms = ('javascript', 'cocoa', 'objc')
+
+ http_ip = data.get('sentry.interfaces.Http', {}).get('env', {}).get('REMOTE_ADDR')
+ if http_ip:
+ data.setdefault('sentry.interfaces.User', {}).setdefault('ip_address', http_ip)
+ elif client_ip and (is_public or data.get('platform') in add_ip_platforms):
+ data.setdefault('sentry.interfaces.User', {}).setdefault('ip_address', client_ip)
+
+ if client_ip and data.get('sdk'):
+ data['sdk']['client_ip'] = client_ip
+
+ # Trim values
+ data['logger'] = trim(data['logger'].strip(), 64)
+ trim_dict(data['extra'], max_size=settings.SENTRY_MAX_EXTRA_VARIABLE_SIZE)
if data['culprit']:
data['culprit'] = trim(data['culprit'], MAX_CULPRIT_LENGTH)
+ if data['transaction']:
+ data['transaction'] = trim(data['transaction'], MAX_CULPRIT_LENGTH)
+
return data
def save(self, project, raw=False):
@@ -411,7 +485,9 @@ def save(self, project, raw=False):
event_id = data.pop('event_id')
level = data.pop('level')
- culprit = data.pop('culprit', None)
+ culprit = data.pop('transaction', None)
+ if not culprit:
+ culprit = data.pop('culprit', None)
logger_name = data.pop('logger', None)
server_name = data.pop('server_name', None)
site = data.pop('site', None)
@@ -434,7 +510,8 @@ def save(self, project, raw=False):
else:
transaction_name = culprit
- date = datetime.fromtimestamp(data.pop('timestamp'))
+ recorded_timestamp = data.pop('timestamp')
+ date = datetime.fromtimestamp(recorded_timestamp)
date = date.replace(tzinfo=timezone.utc)
kwargs = {
@@ -566,6 +643,7 @@ def save(self, project, raw=False):
event.message = message
kwargs['message'] = message
+ received_timestamp = event.data.get('received') or float(event.datetime.strftime('%s'))
group_kwargs = kwargs.copy()
group_kwargs.update(
{
@@ -576,8 +654,7 @@ def save(self, project, raw=False):
'first_seen': date,
'active_at': date,
'data': {
- 'last_received':
- event.data.get('received') or float(event.datetime.strftime('%s')),
+ 'last_received': received_timestamp,
'type':
event_type.key,
# we cache the events metadata on the group to ensure its
@@ -591,18 +668,63 @@ def save(self, project, raw=False):
if release:
group_kwargs['first_release'] = release
- group, is_new, is_regression, is_sample = self._save_aggregate(
- event=event, hashes=hashes, release=release, **group_kwargs
- )
+ try:
+ group, is_new, is_regression, is_sample = self._save_aggregate(
+ event=event, hashes=hashes, release=release, **group_kwargs
+ )
+ except HashDiscarded:
+ event_discarded.send_robust(
+ project=project,
+ sender=EventManager,
+ )
+
+ metrics.incr(
+ 'events.discarded',
+ skip_internal=True,
+ tags={
+ 'organization_id': project.organization_id,
+ 'platform': platform,
+ },
+ )
+ raise
+ else:
+ event_saved.send_robust(
+ project=project,
+ sender=EventManager,
+ )
event.group = group
# store a reference to the group id to guarantee validation of isolation
event.data.bind_ref(event)
- try:
- with transaction.atomic(using=router.db_for_write(EventMapping)):
- EventMapping.objects.create(project=project, group=group, event_id=event_id)
- except IntegrityError:
+ # When an event was sampled, the canonical source of truth
+ # is the EventMapping table since we aren't going to be writing out an actual
+ # Event row. Otherwise, if the Event isn't being sampled, we can safely
+ # rely on the Event table itself as the source of truth and ignore
+ # EventMapping since it's redundant information.
+ if is_sample:
+ try:
+ with transaction.atomic(using=router.db_for_write(EventMapping)):
+ EventMapping.objects.create(project=project, group=group, event_id=event_id)
+ except IntegrityError:
+ self.logger.info(
+ 'duplicate.found',
+ exc_info=True,
+ extra={
+ 'event_uuid': event_id,
+ 'project_id': project.id,
+ 'group_id': group.id,
+ 'model': EventMapping.__name__,
+ }
+ )
+ return event
+
+ # We now always need to check the Event table for dupes
+ # since EventMapping isn't exactly the canonical source of truth.
+ if Event.objects.filter(
+ project_id=project.id,
+ event_id=event_id,
+ ).exists():
self.logger.info(
'duplicate.found',
exc_info=True,
@@ -610,7 +732,7 @@ def save(self, project, raw=False):
'event_uuid': event_id,
'project_id': project.id,
'group_id': group.id,
- 'model': EventMapping.__name__,
+ 'model': Event.__name__,
}
)
return event
@@ -620,6 +742,11 @@ def save(self, project, raw=False):
name=environment,
)
+ group_environment, is_new_group_environment = GroupEnvironment.get_or_create(
+ group_id=group.id,
+ environment_id=environment.id,
+ )
+
if release:
ReleaseEnvironment.get_or_create(
project=project,
@@ -643,7 +770,7 @@ def save(self, project, raw=False):
if release:
counters.append((tsdb.models.release, release.id))
- tsdb.incr_multi(counters, timestamp=event.datetime)
+ tsdb.incr_multi(counters, timestamp=event.datetime, environment_id=environment.id)
frequencies = [
# (tsdb.models.frequent_projects_by_organization, {
@@ -701,6 +828,7 @@ def save(self, project, raw=False):
organization_id=project.organization_id,
project_id=project.id,
group_id=group.id,
+ environment_id=environment.id,
event_id=event.id,
tags=tags,
)
@@ -711,7 +839,8 @@ def save(self, project, raw=False):
(tsdb.models.users_affected_by_group, group.id, (event_user.tag_value, )),
(tsdb.models.users_affected_by_project, project.id, (event_user.tag_value, )),
),
- timestamp=event.datetime
+ timestamp=event.datetime,
+ environment_id=environment.id,
)
if is_new and release:
@@ -722,7 +851,7 @@ def save(self, project, raw=False):
}
)
- safe_execute(Group.objects.add_tags, group, tags, _with_transaction=False)
+ safe_execute(Group.objects.add_tags, group, environment, tags, _with_transaction=False)
if not raw:
if not project.first_event:
@@ -735,6 +864,7 @@ def save(self, project, raw=False):
is_new=is_new,
is_sample=is_sample,
is_regression=is_regression,
+ is_new_group_environment=is_new_group_environment,
)
else:
self.logger.info('post_process.skip.raw_event', extra={'event_id': event.id})
@@ -743,6 +873,14 @@ def save(self, project, raw=False):
if is_regression and not raw:
regression_signal.send_robust(sender=Group, instance=group)
+ metrics.timing(
+ 'events.latency',
+ received_timestamp - recorded_timestamp,
+ tags={
+ 'project_id': project.id,
+ },
+ )
+
return event
def _get_event_user(self, project, data):
@@ -861,21 +999,17 @@ def _save_aggregate(self, event, hashes, release, **kwargs):
**kwargs
), True
+ metrics.incr(
+ 'group.created',
+ skip_internal=True,
+ tags={'platform': event.platform or 'unknown'}
+ )
+
else:
group = Group.objects.get(id=existing_group_id)
group_is_new = False
- # Keep a set of all of the hashes that are relevant for this event and
- # belong to the destination group so that we can record this as the
- # last processed event for each. (We can't just update every
- # ``GroupHash`` instance, since we only want to record this for events
- # that not only include the hash but were also placed into the
- # associated group.)
- relevant_group_hashes = set(
- [instance for instance in all_hashes if instance.group_id == group.id]
- )
-
# If all hashes are brand new we treat this event as new
is_new = False
new_hashes = [h for h in all_hashes if h.group_id is None]
@@ -898,11 +1032,6 @@ def _save_aggregate(self, event, hashes, release, **kwargs):
if group_is_new and len(new_hashes) == len(all_hashes):
is_new = True
- # XXX: This can lead to invalid results due to a race condition and
- # lack of referential integrity enforcement, see above comment(s)
- # about "hash stealing".
- relevant_group_hashes.update(new_hashes)
-
# XXX(dcramer): it's important this gets called **before** the aggregate
# is processed as otherwise values like last_seen will get mutated
can_sample = (
@@ -931,8 +1060,7 @@ def _save_aggregate(self, event, hashes, release, **kwargs):
if not is_sample:
GroupHash.record_last_processed_event_id(
- project.id,
- [h.id for h in relevant_group_hashes],
+ all_hashes[0].id,
event.event_id,
)
diff --git a/src/sentry/eventtypes/__init__.py b/src/sentry/eventtypes/__init__.py
index bb56f3672603ff..47b3c9d59fe82d 100644
--- a/src/sentry/eventtypes/__init__.py
+++ b/src/sentry/eventtypes/__init__.py
@@ -1,13 +1,15 @@
from __future__ import absolute_import
from .base import DefaultEvent
-from .csp import CspEvent
+from .security import CspEvent, ExpectCTEvent, ExpectStapleEvent
from .error import ErrorEvent
from .manager import EventTypeManager
# types are ordered by priority, default should always be last
default_manager = EventTypeManager()
default_manager.register(CspEvent)
+default_manager.register(ExpectCTEvent)
+default_manager.register(ExpectStapleEvent)
default_manager.register(ErrorEvent)
default_manager.register(DefaultEvent)
diff --git a/src/sentry/eventtypes/csp.py b/src/sentry/eventtypes/csp.py
deleted file mode 100644
index 7f5ce2bb36b069..00000000000000
--- a/src/sentry/eventtypes/csp.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from __future__ import absolute_import
-
-from .base import BaseEvent
-
-
-class CspEvent(BaseEvent):
- key = 'csp'
-
- def has_metadata(self):
- return 'sentry.interfaces.Csp' in self.data
-
- def get_metadata(self):
- # TODO(dcramer): we need to avoid importing interfaces in this module
- # due to recursion at top level
- from sentry.interfaces.csp import Csp
- # TODO(dcramer): pull get message into here to avoid instantiation
- # or ensure that these get interfaces passed instead of raw data
- csp = Csp.to_python(self.data['sentry.interfaces.Csp'])
-
- return {
- 'directive': csp.effective_directive,
- 'uri': csp._normalized_blocked_uri,
- 'message': csp.get_message(),
- }
-
- def to_string(self, metadata):
- return metadata['message']
diff --git a/src/sentry/eventtypes/security.py b/src/sentry/eventtypes/security.py
new file mode 100644
index 00000000000000..8225039659f90e
--- /dev/null
+++ b/src/sentry/eventtypes/security.py
@@ -0,0 +1,62 @@
+from __future__ import absolute_import
+
+from .base import BaseEvent
+
+
+class CspEvent(BaseEvent):
+ key = 'csp'
+
+ def has_metadata(self):
+ # TODO(alexh) also look for 'csp' ?
+ return 'sentry.interfaces.Csp' in self.data
+
+ def get_metadata(self):
+ from sentry.interfaces.security import Csp
+ # TODO(dcramer): pull get message into here to avoid instantiation
+ # or ensure that these get interfaces passed instead of raw data
+ csp = Csp.to_python(self.data['sentry.interfaces.Csp'])
+
+ return {
+ 'directive': csp.effective_directive,
+ 'uri': csp._normalized_blocked_uri,
+ 'message': csp.get_message(),
+ }
+
+ def to_string(self, metadata):
+ return metadata['message']
+
+
+class ExpectCTEvent(BaseEvent):
+ key = 'expectct'
+
+ def has_metadata(self):
+ return 'expectct' in self.data
+
+ def get_metadata(self):
+ from sentry.interfaces.security import ExpectCT
+ expectct = ExpectCT.to_python(self.data['expectct'])
+ return {
+ 'origin': expectct.get_origin(),
+ 'message': expectct.get_message(),
+ }
+
+ def to_string(self, metadata):
+ return metadata['message']
+
+
+class ExpectStapleEvent(BaseEvent):
+ key = 'expectstaple'
+
+ def has_metadata(self):
+ return 'expectstaple' in self.data
+
+ def get_metadata(self):
+ from sentry.interfaces.security import ExpectStaple
+ expectstaple = ExpectStaple.to_python(self.data['expectstaple'])
+ return {
+ 'origin': expectstaple.get_origin(),
+ 'message': expectstaple.get_message(),
+ }
+
+ def to_string(self, metadata):
+ return metadata['message']
diff --git a/src/sentry/features/__init__.py b/src/sentry/features/__init__.py
index 55ecf38bf6125a..d38633128e082e 100644
--- a/src/sentry/features/__init__.py
+++ b/src/sentry/features/__init__.py
@@ -9,12 +9,17 @@
default_manager.add('organizations:api-keys', OrganizationFeature) # NOQA
default_manager.add('organizations:create')
default_manager.add('organizations:sso', OrganizationFeature) # NOQA
-default_manager.add('organizations:saml2', OrganizationFeature) # NOQA
+default_manager.add('organizations:sso-saml2', OrganizationFeature) # NOQA
+default_manager.add('organizations:sso-rippling', OrganizationFeature) # NOQA
default_manager.add('organizations:onboarding', OrganizationFeature) # NOQA
default_manager.add('organizations:repos', OrganizationFeature) # NOQA
default_manager.add('organizations:release-commits', OrganizationFeature) # NOQA
default_manager.add('organizations:group-unmerge', OrganizationFeature) # NOQA
+default_manager.add('organizations:invite-members', OrganizationFeature) # NOQA
+default_manager.add('organizations:new-settings', OrganizationFeature) # NOQA
default_manager.add('organizations:integrations-v3', OrganizationFeature) # NOQA
+default_manager.add('organizations:require-2fa', OrganizationFeature) # NOQA
+default_manager.add('organizations:internal-catchall', OrganizationFeature) # NOQA
default_manager.add('projects:similarity-view', ProjectFeature) # NOQA
default_manager.add('projects:global-events', ProjectFeature) # NOQA
default_manager.add('projects:plugins', ProjectPluginFeature) # NOQA
@@ -22,9 +27,12 @@
default_manager.add('projects:rate-limits', ProjectFeature) # NOQA
default_manager.add('workflow:release-emails', ProjectFeature) # NOQA
default_manager.add('projects:sample-events', ProjectFeature) # NOQA
+default_manager.add('projects:servicehooks', ProjectFeature) # NOQA
default_manager.add('projects:similarity-indexing', ProjectFeature) # NOQA
-default_manager.add('projects:custom-filters', ProjectFeature) # NOQA
+default_manager.add('projects:discard-groups', ProjectFeature) # NOQA
default_manager.add('projects:custom-inbound-filters', ProjectFeature) # NOQA
+default_manager.add('projects:minidump', ProjectFeature) # NOQA
+default_manager.add('organizations:environments', OrganizationFeature) # NOQA
# expose public api
add = default_manager.add
diff --git a/src/sentry/filestore/s3.py b/src/sentry/filestore/s3.py
index 9a19cec2b3b170..99c882658130b7 100644
--- a/src/sentry/filestore/s3.py
+++ b/src/sentry/filestore/s3.py
@@ -41,7 +41,6 @@
import mimetypes
import threading
from gzip import GzipFile
-from tempfile import SpooledTemporaryFile
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
@@ -156,11 +155,7 @@ def size(self):
def _get_file(self):
if self._file is None:
- self._file = SpooledTemporaryFile(
- max_size=self._storage.max_memory_size,
- suffix=".S3Boto3StorageFile",
- dir=None,
- )
+ self._file = BytesIO()
if 'r' in self._mode:
self._is_dirty = False
self._file.write(self.obj.get()['Body'].read())
@@ -245,6 +240,10 @@ class S3Boto3Storage(Storage):
mode and supports streaming(buffering) data in chunks to S3
when writing.
"""
+ # XXX: note that this file reads entirely into memory before the first
+ # read happens. This means that it should only be used for small
+ # files (eg: see how sentry.models.file works with it through the
+ # ChunkedFileBlobIndexWrapper.
connection_class = staticmethod(resource)
connection_service_name = 's3'
default_content_type = 'application/octet-stream'
@@ -286,10 +285,6 @@ class S3Boto3Storage(Storage):
region_name = None
use_ssl = True
- # The max amount of memory a returned file can take up before being
- # rolled over into a temporary file on disk. Default is 0: Do not roll over.
- max_memory_size = 0
-
def __init__(self, acl=None, bucket=None, **settings):
# check if some of the settings we've provided as class attributes
# need to be overwritten with values passed in here
@@ -330,14 +325,22 @@ def connection(self):
# urllib/requests libraries read. See https://github.com/boto/boto3/issues/338
# and http://docs.python-requests.org/en/latest/user/advanced/#proxies
if self._connection is None:
+
+ # If this is running on an ec2 instance, allow boto to connect using an IAM role
+ # instead of explicitly provided an access key and secret
+ # http://boto3.readthedocs.io/en/latest/guide/configuration.html#iam-role
+ kwargs = {}
+ if self.access_key and self.secret_key:
+ kwargs['aws_access_key_id'] = self.access_key
+ kwargs['aws_secret_access_key'] = self.secret_key
+
self._connection = self.connection_class(
self.connection_service_name,
- aws_access_key_id=self.access_key,
- aws_secret_access_key=self.secret_key,
region_name=self.region_name,
use_ssl=self.use_ssl,
endpoint_url=self.endpoint_url,
- config=self.config
+ config=self.config,
+ **kwargs
)
return self._connection
diff --git a/src/sentry/filters/localhost.py b/src/sentry/filters/localhost.py
index c12d2d824b8b1c..9233a8829521ec 100644
--- a/src/sentry/filters/localhost.py
+++ b/src/sentry/filters/localhost.py
@@ -10,8 +10,8 @@
class LocalhostFilter(Filter):
id = FilterStatKeys.LOCALHOST
- name = 'Filter out errors coming from localhost'
- description = 'This applies to to both IPv4 (``127.0.0.1``) and IPv6 (``::1``) addresses.'
+ name = 'Filter out events coming from localhost'
+ description = 'This applies to both IPv4 (``127.0.0.1``) and IPv6 (``::1``) addresses.'
def get_ip_address(self, data):
try:
diff --git a/src/sentry/filters/preprocess_hashes.py b/src/sentry/filters/preprocess_hashes.py
deleted file mode 100644
index 383cd1836abc27..00000000000000
--- a/src/sentry/filters/preprocess_hashes.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import absolute_import
-
-from django.core.cache import cache, get_cache, InvalidCacheBackendError
-
-
-try:
- hash_cache = get_cache('preprocess_hash')
-except InvalidCacheBackendError:
- hash_cache = cache
-
-
-def get_raw_cache_key(project_id, event_id):
- return 'e:raw:{1}:{0}'.format(project_id, event_id)
diff --git a/src/sentry/filters/web_crawlers.py b/src/sentry/filters/web_crawlers.py
index 51d13fc8162db6..59703a81b7b714 100644
--- a/src/sentry/filters/web_crawlers.py
+++ b/src/sentry/filters/web_crawlers.py
@@ -19,6 +19,7 @@
r'Google',
# Bing search
r'BingBot',
+ r'BingPreview',
# Baidu search
r'Baiduspider',
# Yahoo
diff --git a/src/sentry/http.py b/src/sentry/http.py
index 2c730741676288..a6b4f955f257ad 100644
--- a/src/sentry/http.py
+++ b/src/sentry/http.py
@@ -366,13 +366,4 @@ def fetch_file(
if response is not None:
response.close()
- if result[2] != 200:
- logger.debug('HTTP %s when fetching %r', result[2], url, exc_info=True)
- error = {
- 'type': EventError.FETCH_INVALID_HTTP_CODE,
- 'value': result[2],
- 'url': expose_url(url),
- }
- raise CannotFetch(error)
-
return UrlResult(url, result[0], result[1], result[2], result[3])
diff --git a/src/sentry/identity/__init__.py b/src/sentry/identity/__init__.py
new file mode 100644
index 00000000000000..1efa68ad3c5291
--- /dev/null
+++ b/src/sentry/identity/__init__.py
@@ -0,0 +1,19 @@
+from __future__ import absolute_import
+
+from .base import * # NOQA
+from .manager import IdentityManager # NOQA
+from .oauth2 import * # NOQA
+
+from .slack import * # NOQA
+
+
+default_manager = IdentityManager()
+all = default_manager.all
+get = default_manager.get
+exists = default_manager.exists
+register = default_manager.register
+unregister = default_manager.unregister
+
+# TODO(epurkhiser): Should this be moved into it's own plugin, it should be
+# initialized there.
+register(SlackIdentityProvider) # NOQA
diff --git a/src/sentry/identity/base.py b/src/sentry/identity/base.py
new file mode 100644
index 00000000000000..bf1d6dce2148ea
--- /dev/null
+++ b/src/sentry/identity/base.py
@@ -0,0 +1,89 @@
+from __future__ import absolute_import, print_function
+
+import logging
+from collections import namedtuple
+
+from sentry.utils.pipeline import PipelineProvider
+
+
+class MigratingIdentityId(namedtuple('MigratingIdentityId', ['id', 'legacy_id'])):
+ """
+ MigratingIdentityId may be used in the ``id`` field of an identity
+ dictionary to facilitate migrating user identites from one identifying id
+ to another.
+ """
+ __slots__ = ()
+
+ def __unicode__(self):
+ # Default to id when coercing for query lookup
+ return self.id
+
+
+class Provider(PipelineProvider):
+ """
+ A provider indicates how identity authenticate should happen for a given service.
+ """
+
+ # The unique identifier of the provider
+ key = None
+
+ # A human readable name for this provider
+ name = None
+
+ def __init__(self, **config):
+ self.config = config
+ self.logger = logging.getLogger('sentry.identity.%s'.format(self.key))
+
+ def get_pipeline(self):
+ """
+ Return a list of AuthView instances representing the authentication
+ pipeline for this provider.
+ """
+ raise NotImplementedError
+
+ def build_identity(self, state):
+ """
+ Return a mapping containing the identity information.
+
+ - ``state`` is the resulting data captured by the pipeline
+
+ >>> {
+ >>> "id": "foo@example.com",
+ >>> "email": "foo@example.com",
+ >>> "name": "Foo Bar",
+ >>> "scopes": ['emaill', ...],
+ >>> "data": { ... },
+ >>> }
+
+ The ``id`` key is required.
+
+ The ``id`` may be passed in as a ``MigratingIdentityId`` should the
+ the id key be migrating from one value to another and have multiple
+ lookup values.
+
+ If the identity can not be constructed an ``IdentityNotValid`` error
+ should be raised.
+ """
+ raise NotImplementedError
+
+ def update_identity(self, new_data, current_data):
+ """
+ When re-authenticating with a provider, the identity data may need to
+ be mutated based on the previous state. An example of this is Google,
+ which will not return a `refresh_token` unless the user explicitly
+ goes through an approval process.
+
+ Return the new state which should be used for an identity.
+ """
+ return new_data
+
+ def refresh_identity(self, auth_identity):
+ """
+ Updates the AuthIdentity with any changes from upstream. The primary
+ example of a change would be signalling this identity is no longer
+ valid.
+
+ If the identity is no longer valid an ``IdentityNotValid`` error should
+ be raised.
+ """
+ raise NotImplementedError
diff --git a/src/sentry/identity/manager.py b/src/sentry/identity/manager.py
new file mode 100644
index 00000000000000..cd660f04f71d79
--- /dev/null
+++ b/src/sentry/identity/manager.py
@@ -0,0 +1,44 @@
+from __future__ import absolute_import, print_function
+
+__all__ = ['IdentityManager']
+
+import six
+
+from sentry.exceptions import NotRegistered
+
+
+class IdentityManager(object):
+ def __init__(self):
+ self.__values = {}
+
+ def __iter__(self):
+ return iter(self.all())
+
+ def all(self):
+ for key in six.iterkeys(self.__values):
+ provider = self.get(key)
+ if provider.is_configured():
+ yield provider
+
+ def get(self, key, **kwargs):
+ try:
+ cls = self.__values[key]
+ except KeyError:
+ raise NotRegistered(key)
+ return cls(**kwargs)
+
+ def exists(self, key):
+ return key in self.__values
+
+ def register(self, cls):
+ self.__values[cls.key] = cls
+
+ def unregister(self, cls):
+ try:
+ if self.__values[cls.key] != cls:
+ # dont allow unregistering of arbitrary provider
+ raise NotRegistered(cls.key)
+ except KeyError:
+ # we gracefully handle a missing provider
+ return
+ del self.__values[cls.key]
diff --git a/src/sentry/integrations/oauth.py b/src/sentry/identity/oauth2.py
similarity index 60%
rename from src/sentry/integrations/oauth.py
rename to src/sentry/identity/oauth2.py
index fd80cb6c652a2e..34896684488852 100644
--- a/src/sentry/integrations/oauth.py
+++ b/src/sentry/identity/oauth2.py
@@ -1,68 +1,70 @@
from __future__ import absolute_import, print_function
-__all__ = ['OAuth2Integration', 'OAuth2CallbackView', 'OAuth2LoginView']
+__all__ = ['OAuth2Provider', 'OAuth2CallbackView', 'OAuth2LoginView']
from six.moves.urllib.parse import parse_qsl, urlencode
from uuid import uuid4
+from time import time
from sentry.http import safe_urlopen, safe_urlread
from sentry.utils import json
from sentry.utils.http import absolute_uri
+from sentry.utils.pipeline import PipelineView
-from .base import Integration
-from .view import PipelineView
+from .base import Provider
ERR_INVALID_STATE = 'An error occurred while validating your request.'
-class OAuth2Integration(Integration):
+class OAuth2Provider(Provider):
+ """
+ The OAuth2Provider is a generic way to implement an identity provider that
+ uses the OAuth 2.0 protocol as a means for authenticating a user.
+
+ OAuth scopes are configured through the oauth_scopes class property,
+ however may be overriden using the ``config['oauth_scopes']`` object.
+ """
oauth_access_token_url = ''
oauth_authorize_url = ''
- oauth_client_id = ''
- oauth_client_secret = ''
- oauth_refresh_token_url = ''
- oauth_scopes = ()
+ refresh_token_url = ''
- def is_configured(self):
- return (
- self.get_oauth_client_id() and
- self.get_oauth_client_secret() and
- self.get_oauth_access_token_url() and
- self.get_oauth_authorize_url()
- )
+ oauth_scopes = ()
def get_oauth_client_id(self):
- return self.oauth_client_id
+ raise NotImplementedError
def get_oauth_client_secret(self):
- return self.oauth_client_secret
-
- def get_oauth_access_token_url(self):
- return self.oauth_access_token_url
-
- def get_oauth_authorize_url(self):
- return self.oauth_authorize_url
-
- def get_oauth_refresh_token_url(self):
- return self.oauth_refresh_token_url
+ raise NotImplementedError
def get_oauth_scopes(self):
- return self.oauth_scopes
+ return self.config.get('oauth_scopes', self.oauth_scopes)
- def get_pipeline(self):
+ def get_pipeline_views(self):
return [
OAuth2LoginView(
- authorize_url=self.get_oauth_authorize_url(),
+ authorize_url=self.oauth_authorize_url,
client_id=self.get_oauth_client_id(),
scope=' '.join(self.get_oauth_scopes()),
),
OAuth2CallbackView(
- access_token_url=self.get_oauth_access_token_url(),
+ access_token_url=self.oauth_access_token_url,
client_id=self.get_oauth_client_id(),
client_secret=self.get_oauth_client_secret(),
),
]
+ def get_oauth_data(self, payload):
+ data = {'access_token': payload['access_token']}
+
+ if 'expires_in' in payload:
+ data['expires'] = int(time()) + payload['expires_in']
+ if 'refresh_token' in payload:
+ data['refresh_token'] = payload['refresh_token']
+ if 'token_type' in payload:
+ data['token_type'] = payload['token_type']
+
+ return data
+
class OAuth2LoginView(PipelineView):
authorize_url = None
@@ -93,19 +95,19 @@ def get_authorize_params(self, state, redirect_uri):
'redirect_uri': redirect_uri,
}
- def dispatch(self, request, helper):
+ def dispatch(self, request, pipeline):
if 'code' in request.GET:
- return helper.next_step()
+ return pipeline.next_step()
state = uuid4().hex
params = self.get_authorize_params(
state=state,
- redirect_uri=absolute_uri(helper.get_redirect_url()),
+ redirect_uri=absolute_uri(pipeline.redirect_url()),
)
redirect_uri = '{}?{}'.format(self.get_authorize_url(), urlencode(params))
- helper.bind_state('state', state)
+ pipeline.bind_state('state', state)
return self.redirect(redirect_uri)
@@ -133,11 +135,11 @@ def get_token_params(self, code, redirect_uri):
'client_secret': self.client_secret,
}
- def exchange_token(self, request, helper, code):
+ def exchange_token(self, request, pipeline, code):
# TODO: this needs the auth yet
data = self.get_token_params(
code=code,
- redirect_uri=absolute_uri(helper.get_redirect_url()),
+ redirect_uri=absolute_uri(pipeline.redirect_url()),
)
req = safe_urlopen(self.access_token_url, data=data)
body = safe_urlread(req)
@@ -145,41 +147,33 @@ def exchange_token(self, request, helper, code):
return dict(parse_qsl(body))
return json.loads(body)
- def dispatch(self, request, helper):
+ def dispatch(self, request, pipeline):
error = request.GET.get('error')
state = request.GET.get('state')
code = request.GET.get('code')
if error:
- helper.logger.info('auth.token-exchange-error', extra={
- 'error': error,
- })
- return helper.error(error)
+ pipeline.logger.info('identity.token-exchange-error', extra={'error': error})
+ return pipeline.error(error)
- if state != helper.fetch_state('state'):
- helper.logger.info('auth.token-exchange-error', extra={
- 'error': 'invalid_state',
- })
- return helper.error(ERR_INVALID_STATE)
+ if state != pipeline.fetch_state('state'):
+ pipeline.logger.info('identity.token-exchange-error', extra={'error': 'invalid_state'})
+ return pipeline.error(ERR_INVALID_STATE)
- data = self.exchange_token(request, helper, code)
+ data = self.exchange_token(request, pipeline, code)
if 'error_description' in data:
- helper.logger.info('auth.token-exchange-error', extra={
- 'error': data.get('error'),
- })
- return helper.error(data['error_description'])
+ error = data.get('error')
+ pipeline.logger.info('identity.token-exchange-error', extra={'error': error})
+ return pipeline.error(data['error_description'])
if 'error' in data:
- helper.logger.info('auth.token-exchange-error', extra={
- 'error': data['error'],
- })
- return helper.error(
- 'There was an error when retrieving a token from the upstream service.')
+ pipeline.logger.info('identity.token-exchange-error', extra={'error': data['error']})
+ return pipeline.error('Failed to retrieve toek from the upstream service.')
# we can either expect the API to be implicit and say "im looking for
# blah within state data" or we need to pass implementation + call a
# hook here
- helper.bind_state('data', data)
+ pipeline.bind_state('data', data)
- return helper.next_step()
+ return pipeline.next_step()
diff --git a/src/sentry/identity/pipeline.py b/src/sentry/identity/pipeline.py
new file mode 100644
index 00000000000000..967f02f6bfa4dc
--- /dev/null
+++ b/src/sentry/identity/pipeline.py
@@ -0,0 +1,60 @@
+from __future__ import absolute_import, print_function
+
+import logging
+
+from django.contrib import messages
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+from django.utils import timezone
+from django.utils.translation import ugettext_lazy as _
+
+from sentry.utils.pipeline import Pipeline
+from sentry.models import Identity, IdentityStatus, IdentityProvider
+
+from . import default_manager
+
+IDENTITY_LINKED = _("Your {identity_provider} account has been associated with your Sentry account")
+
+logger = logging.getLogger('sentry.identity')
+
+
+class IdentityProviderPipeline(Pipeline):
+ logger = logger
+
+ pipeline_name = 'identity_provider'
+ provider_manager = default_manager
+ provider_model_cls = IdentityProvider
+
+ def redirect_url(self):
+ associate_url = reverse('sentry-account-link-identity')
+
+ # Use configured redirect_url if specified for the pipeline if available
+ return self.config.get('redirect_url', associate_url)
+
+ def finish_pipeline(self):
+ identity = self.provider.build_identity(self.state.data)
+
+ defaults = {
+ 'status': IdentityStatus.VALID,
+ 'scopes': identity.get('scopes', []),
+ 'data': identity.get('data', {}),
+ 'date_verified': timezone.now(),
+ }
+
+ identity, created = Identity.objects.get_or_create(
+ idp=self.provider_model,
+ user=self.request.user,
+ external_id=identity['id'],
+ defaults=defaults,
+ )
+
+ if not created:
+ identity.update(**defaults)
+
+ messages.add_message(self.request, messages.SUCCESS, IDENTITY_LINKED.format(
+ identity_provider=self.provider.name,
+ ))
+
+ self.state.clear()
+
+ return HttpResponseRedirect(reverse('sentry-account-settings-identities'))
diff --git a/src/sentry/identity/providers/__init__.py b/src/sentry/identity/providers/__init__.py
new file mode 100644
index 00000000000000..1422fb52eff3a3
--- /dev/null
+++ b/src/sentry/identity/providers/__init__.py
@@ -0,0 +1,5 @@
+from __future__ import absolute_import
+
+from sentry.utils.imports import import_submodules
+
+import_submodules(globals(), __name__, __path__)
diff --git a/src/sentry/identity/providers/dummy.py b/src/sentry/identity/providers/dummy.py
new file mode 100644
index 00000000000000..4db414b73ee45b
--- /dev/null
+++ b/src/sentry/identity/providers/dummy.py
@@ -0,0 +1,34 @@
+from __future__ import absolute_import, print_function
+
+__all__ = ['DummyProvider']
+
+from django.http import HttpResponse
+
+from sentry.identity.base import Provider
+from sentry.utils.pipeline import PipelineView
+
+
+class AskEmail(PipelineView):
+ def dispatch(self, request, pipeline):
+ if 'email' in request.POST:
+ pipeline.bind_state('email', request.POST.get('email'))
+ return pipeline.next_step()
+
+ return HttpResponse(DummyProvider.TEMPLATE)
+
+
+class DummyProvider(Provider):
+ name = 'Dummy'
+ key = 'dummy'
+
+ TEMPLATE = ''
+
+ def get_pipeline_views(self):
+ return [AskEmail()]
+
+ def build_identity(self, state):
+ return {
+ 'id': state['email'],
+ 'email': state['email'],
+ 'name': 'Dummy',
+ }
diff --git a/src/sentry/identity/slack/__init__.py b/src/sentry/identity/slack/__init__.py
new file mode 100644
index 00000000000000..1422fb52eff3a3
--- /dev/null
+++ b/src/sentry/identity/slack/__init__.py
@@ -0,0 +1,5 @@
+from __future__ import absolute_import
+
+from sentry.utils.imports import import_submodules
+
+import_submodules(globals(), __name__, __path__)
diff --git a/src/sentry/identity/slack/provider.py b/src/sentry/identity/slack/provider.py
new file mode 100644
index 00000000000000..96d56fa3d4a13b
--- /dev/null
+++ b/src/sentry/identity/slack/provider.py
@@ -0,0 +1,39 @@
+from __future__ import absolute_import
+
+from sentry import options
+from sentry.options.manager import FLAG_PRIORITIZE_DISK
+from sentry.identity.oauth2 import OAuth2Provider
+
+options.register('slack.client-id', flags=FLAG_PRIORITIZE_DISK)
+options.register('slack.client-secret', flags=FLAG_PRIORITIZE_DISK)
+options.register('slack.verification-token', flags=FLAG_PRIORITIZE_DISK)
+
+
+class SlackIdentityProvider(OAuth2Provider):
+ key = 'slack'
+ name = 'Slack'
+
+ oauth_access_token_url = 'https://slack.com/api/oauth.access'
+ oauth_authorize_url = 'https://slack.com/oauth/authorize'
+
+ oauth_scopes = (
+ 'identity.basic',
+ 'identity.email',
+ )
+
+ def get_oauth_client_id(self):
+ return options.get('slack.client-id')
+
+ def get_oauth_client_secret(self):
+ return options.get('slack.client-secret')
+
+ def build_identity(self, data):
+ data = data['data']
+
+ return {
+ 'type': 'slack',
+ 'id': data['user']['id'],
+ 'email': data['user']['email'],
+ 'scopes': sorted(data['scope'].split(',')),
+ 'data': self.get_oauth_data(data),
+ }
diff --git a/src/sentry/integrations/__init__.py b/src/sentry/integrations/__init__.py
index 0bb8eb4359b2e7..b4a4a4df137e62 100644
--- a/src/sentry/integrations/__init__.py
+++ b/src/sentry/integrations/__init__.py
@@ -2,8 +2,6 @@
from .base import * # NOQA
from .manager import IntegrationManager # NOQA
-from .oauth import * # NOQA
-from .view import * # NOQA
default_manager = IntegrationManager()
diff --git a/src/sentry/integrations/base.py b/src/sentry/integrations/base.py
index e38b7092bb62ac..340f026778fb49 100644
--- a/src/sentry/integrations/base.py
+++ b/src/sentry/integrations/base.py
@@ -4,13 +4,16 @@
import logging
+from sentry.utils.pipeline import PipelineProvider
-class Integration(object):
+
+class Integration(PipelineProvider):
"""
An integration describes a third party that can be registered within Sentry.
- The core behavior is simply how to add the integration (the authentication
- pipeline), and what kind of configuration is stored.
+ The core behavior is simply how to add the integration (the setup
+ pipeline), which will likely use a nested pipeline for identity
+ authentication, and what kind of configuration is stored.
This is similar to Sentry's legacy 'plugin' information, except that an
integration is lives as an instance in the database, and the ``Integration``
@@ -19,7 +22,7 @@ class is just a descriptor for how that object functions, and what behavior
"""
# a unique identifier (e.g. 'slack')
- id = None
+ key = None
# a human readable name (e.g. 'Slack')
name = None
@@ -31,14 +34,14 @@ class is just a descriptor for how that object functions, and what behavior
}
def get_logger(self):
- return logging.getLogger('sentry.integration.%s' % (self.get_id(), ))
+ return logging.getLogger('sentry.integration.%s' % (self.key, ))
- def get_pipeline(self):
+ def get_pipeline_views(self):
"""
Return a list of ``View`` instances describing this integration's
configuration pipeline.
- >>> def get_pipeline(self):
+ >>> def get_pipeline_views(self):
>>> return []
"""
raise NotImplementedError
@@ -97,5 +100,5 @@ def setup(self):
Executed once Sentry has been initialized at runtime.
>>> def setup(self):
- >>> bindings.add('repository.provider', GitHubRepositoryProvider, id='github')
+ >>> bindings.add('repository.provider', GitHubRepositoryProvider, key='github')
"""
diff --git a/src/sentry/integrations/cloudflare/webhook.py b/src/sentry/integrations/cloudflare/webhook.py
index dcb2f9b58bcfa0..ebb2ad4ebf7f5a 100644
--- a/src/sentry/integrations/cloudflare/webhook.py
+++ b/src/sentry/integrations/cloudflare/webhook.py
@@ -79,7 +79,7 @@ def project_from_json(self, request, data, scope='project:write'):
projects = Project.objects.filter(
organization=org,
- team__in=Team.objects.get_for_user(org, request.user, scope='project:write'),
+ teams__in=Team.objects.get_for_user(org, request.user, scope='project:write'),
)
for project in projects:
if six.text_type(project.id) == project_id:
@@ -144,7 +144,7 @@ def on_organization_change(self, request, data, is_test):
projects = sorted(Project.objects.filter(
organization=org,
- team__in=Team.objects.get_for_user(org, request.user, scope='project:write'),
+ teams__in=Team.objects.get_for_user(org, request.user, scope='project:write'),
), key=lambda x: x.slug)
enum_choices = [six.text_type(o.id) for o in projects]
diff --git a/src/sentry/integrations/example/integration.py b/src/sentry/integrations/example/integration.py
index a52fa8993bdffd..3ceb081ad1dcc0 100644
--- a/src/sentry/integrations/example/integration.py
+++ b/src/sentry/integrations/example/integration.py
@@ -1,7 +1,8 @@
from __future__ import absolute_import
from django.http import HttpResponse
-from sentry.integrations import Integration, PipelineView
+from sentry.integrations import Integration
+from sentry.utils.pipeline import PipelineView
class ExampleSetupView(PipelineView):
@@ -14,10 +15,10 @@ class ExampleSetupView(PipelineView):
"""
- def dispatch(self, request, helper):
+ def dispatch(self, request, pipeline):
if 'name' in request.POST:
- helper.bind_state('name', request.POST['name'])
- return helper.next_step()
+ pipeline.bind_state('name', request.POST['name'])
+ return pipeline.next_step()
return HttpResponse(self.TEMPLATE)
@@ -26,11 +27,11 @@ class ExampleIntegration(Integration):
"""
An example integration, generally used for testing.
"""
- id = 'example'
+ key = 'example'
name = 'Example'
- def get_pipeline(self):
+ def get_pipeline_views(self):
return [
ExampleSetupView(),
]
@@ -53,5 +54,5 @@ def setup(self):
Executed once Sentry has been initialized at runtime.
>>> def setup(self):
- >>> bindings.add('repository.provider', GitHubRepositoryProvider, id='github')
+ >>> bindings.add('repository.provider', GitHubRepositoryProvider, key='github')
"""
diff --git a/src/sentry/integrations/helper.py b/src/sentry/integrations/helper.py
deleted file mode 100644
index 5e2ac470aa18fc..00000000000000
--- a/src/sentry/integrations/helper.py
+++ /dev/null
@@ -1,240 +0,0 @@
-from __future__ import absolute_import, print_function
-
-__all__ = ['PipelineHelper']
-
-import json
-import logging
-
-from django.db import IntegrityError, transaction
-from django.http import HttpResponse
-
-from sentry.api.serializers import serialize
-from sentry.models import (
- Identity, IdentityProvider, IdentityStatus, Integration, Organization,
- UserIdentity
-)
-from sentry.utils.hashlib import md5_text
-from sentry.utils.http import absolute_uri
-from sentry.web.helpers import render_to_response
-
-from . import default_manager
-
-SESSION_KEY = 'integration.setup'
-
-DIALOG_RESPONSE = """
-
-
-
-
-
-
-
-"""
-
-logger = logging.getLogger('sentry.integrations')
-
-
-class PipelineHelper(object):
- logger = logger
-
- @classmethod
- def get_for_request(cls, request, provider_id):
- session = request.session.get(SESSION_KEY, {})
- if not session:
- logger.error('integrations.setup.missing-session-data')
- return None
-
- # TODO(dcramer): enforce access check
- organization = Organization.objects.get(
- id=session['org'],
- )
-
- if session.get('int'):
- integration = Integration.objects.get(
- id=session['int'],
- organization_id=organization.id,
- )
- else:
- integration = None
-
- if provider_id != session['pro']:
- logger.error('integrations.setup.invalid-provider')
- return None
-
- if session['uid'] != request.user.id:
- logger.error('integrations.setup.invalid-uid')
- return None
-
- instance = cls(
- request=request,
- organization=organization,
- integration=integration,
- provider_id=provider_id,
- step=session['step'],
- dialog=bool(session['dlg']),
- state=session['state'],
- )
- if instance.signature != session['sig']:
- logger.error('integrations.setup.invalid-signature')
- return None
- return instance
-
- @classmethod
- def initialize(cls, request, organization, provider_id, dialog=False):
- inst = cls(
- request=request,
- organization=organization,
- provider_id=provider_id,
- dialog=dialog,
- )
- inst.save_session()
- return inst
-
- def __init__(self, request, organization, provider_id, integration=None,
- step=0, state=None, dialog=False):
- self.request = request
- self.integration = integration
- self.organization = organization
- self.provider = default_manager.get(provider_id)
- self.pipeline = self.provider.get_pipeline()
- self.signature = md5_text(*[
- '{module}.{name}'.format(
- module=type(v).__module__,
- name=type(v).__name__,
- ) for v in self.pipeline
- ]).hexdigest()
- self.step = step
- self.state = state or {}
- self.dialog = dialog
-
- def save_session(self):
- self.request.session[SESSION_KEY] = {
- 'uid': self.request.user.id,
- 'org': self.organization.id,
- 'pro': self.provider.id,
- 'int': self.integration.id if self.integration else '',
- 'sig': self.signature,
- 'step': self.step,
- 'state': self.state,
- 'dlg': int(self.dialog),
- }
- self.request.session.modified = True
-
- def get_redirect_url(self):
- return absolute_uri('/extensions/{}/setup/'.format(
- self.provider.id,
- ))
-
- def clear_session(self):
- if SESSION_KEY in self.request.session:
- del self.request.session[SESSION_KEY]
- self.request.session.modified = True
-
- def current_step(self):
- """
- Render the current step.
- """
- if self.step == len(self.pipeline):
- return self.finish_pipeline()
- return self.pipeline[self.step].dispatch(
- request=self.request,
- helper=self,
- )
-
- def next_step(self):
- """
- Render the next step.
- """
- self.step += 1
- self.save_session()
- return self.current_step()
-
- def finish_pipeline(self):
- data = self.provider.build_integration(self.state)
- response = self._finish_pipeline(data)
- self.clear_session()
- return response
-
- def respond(self, template, context=None, status=200):
- default_context = {
- 'organization': self.organization,
- 'provider': self.provider,
- }
- if context:
- default_context.update(context)
-
- return render_to_response(template, default_context, self.request, status=status)
-
- def error(self, message):
- # TODO(dcramer): this needs to handle the dialog
- self.clear_session()
- return self._dialog_response({'detail': message}, False)
-
- def bind_state(self, key, value):
- self.state[key] = value
- self.save_session()
-
- def fetch_state(self, key):
- return self.state.get(key)
-
- def _finish_pipeline(self, data):
- if self.integration:
- assert data['external_id'] == self.integration.external_id
- self.integration.update(
- metadata=data.get('metadata', {}),
- name=data.get('name', self.provider.name),
- )
- else:
- defaults = {
- 'metadata': data.get('metadata', {}),
- 'name': data.get('name', data['external_id']),
- }
- self.integration, created = Integration.objects.get_or_create(
- provider=self.provider.id,
- external_id=data['external_id'],
- defaults=defaults
- )
- if not created:
- self.integration.update(**defaults)
- self.integration.add_organization(self.organization.id)
-
- id_config = data.get('identity')
- if id_config:
- idp = IdentityProvider.get(id_config['type'], id_config['instance'])
- identity, created = Identity.objects.get_or_create(
- idp=idp,
- external_id=id_config['external_id'],
- defaults={
- 'status': IdentityStatus.VALID,
- 'scopes': id_config['scopes'],
- 'data': id_config['data'],
- },
- )
- if not created:
- if identity.status != IdentityStatus.VALID:
- identity.update(status=IdentityStatus.VALID)
- try:
- with transaction.atomic():
- UserIdentity.objects.create(
- user=self.request.user,
- identity=identity,
- )
- except IntegrityError:
- pass
-
- return self._dialog_response(serialize(self.integration, self.request.user), True)
-
- def _dialog_response(self, data, success):
- assert self.dialog
- return HttpResponse(
- DIALOG_RESPONSE.format(
- json=json.dumps({
- 'success': success,
- 'data': data,
- })
- ),
- content_type='text/html',
- )
diff --git a/src/sentry/integrations/manager.py b/src/sentry/integrations/manager.py
index aa6e4019639c27..4ab41500536be9 100644
--- a/src/sentry/integrations/manager.py
+++ b/src/sentry/integrations/manager.py
@@ -33,14 +33,14 @@ def exists(self, key):
return key in self.__values
def register(self, cls):
- self.__values[cls.id] = cls
+ self.__values[cls.key] = cls
def unregister(self, cls):
try:
- if self.__values[cls.id] != cls:
+ if self.__values[cls.key] != cls:
# dont allow unregistering of arbitrary provider
- raise NotRegistered(cls.id)
+ raise NotRegistered(cls.key)
except KeyError:
# we gracefully handle a missing provider
return
- del self.__values[cls.id]
+ del self.__values[cls.key]
diff --git a/src/sentry/integrations/pipeline.py b/src/sentry/integrations/pipeline.py
new file mode 100644
index 00000000000000..5ffbc1b9914d49
--- /dev/null
+++ b/src/sentry/integrations/pipeline.py
@@ -0,0 +1,88 @@
+from __future__ import absolute_import, print_function
+
+__all__ = ['IntegrationPipeline']
+
+from django.http import HttpResponse
+from django.utils import timezone
+
+from sentry.api.serializers import serialize
+from sentry.models import Identity, IdentityProvider, IdentityStatus, Integration
+from sentry.utils.pipeline import Pipeline
+from sentry.utils import json
+
+from . import default_manager
+
+DIALOG_RESPONSE = """
+
+
+
+
+
+
+
+"""
+
+
+class IntegrationPipeline(Pipeline):
+ pipeline_name = 'integration_pipeline'
+ provider_manager = default_manager
+
+ def finish_pipeline(self):
+ data = self.provider.build_integration(self.state.data)
+ response = self._finish_pipeline(data)
+ self.clear_session()
+ return response
+
+ def _finish_pipeline(self, data):
+ # Create the new integration
+ defaults = {
+ 'metadata': data.get('metadata', {}),
+ 'name': data.get('name', data['external_id']),
+ }
+ integration, created = Integration.objects.get_or_create(
+ provider=self.provider.key,
+ external_id=data['external_id'],
+ defaults=defaults
+ )
+ if not created:
+ integration.update(**defaults)
+ integration.add_organization(self.organization.id)
+
+ # Does this integration provide a user identity for the user setting up
+ # the integration?
+ identity = data.get('user_identity')
+
+ if identity:
+ # Create identity provider for this integration if nessicary
+ idp, created = IdentityProvider.objects.get_or_create(
+ organization=self.organization,
+ type=identity['type'],
+ )
+
+ identity, created = Identity.objects.get_or_create(
+ idp=idp,
+ user=self.request.user,
+ external_id=identity['external_id'],
+ defaults={
+ 'status': IdentityStatus.VALID,
+ 'scopes': identity['scopes'],
+ 'data': identity['data'],
+ 'date_verified': timezone.now(),
+ },
+ )
+
+ return self._dialog_response(serialize(integration, self.request.user), True)
+
+ def _dialog_response(self, data, success):
+ return HttpResponse(
+ DIALOG_RESPONSE.format(
+ json=json.dumps({
+ 'success': success,
+ 'data': data,
+ })
+ ),
+ content_type='text/html',
+ )
diff --git a/src/sentry/integrations/slack/__init__.py b/src/sentry/integrations/slack/__init__.py
index 1422fb52eff3a3..90d7e3d0fc679c 100644
--- a/src/sentry/integrations/slack/__init__.py
+++ b/src/sentry/integrations/slack/__init__.py
@@ -1,5 +1,10 @@
from __future__ import absolute_import
from sentry.utils.imports import import_submodules
+from sentry.rules import rules
+
+from .notify_action import SlackNotifyServiceAction
import_submodules(globals(), __name__, __path__)
+
+rules.add(SlackNotifyServiceAction)
diff --git a/src/sentry/integrations/slack/action_endpoint.py b/src/sentry/integrations/slack/action_endpoint.py
new file mode 100644
index 00000000000000..b6649263f759b3
--- /dev/null
+++ b/src/sentry/integrations/slack/action_endpoint.py
@@ -0,0 +1,279 @@
+from __future__ import absolute_import
+
+from django.core.urlresolvers import reverse
+
+from sentry import http, options
+from sentry.api import client
+from sentry.api.base import Endpoint
+from sentry.models import Group, Integration, Project, IdentityProvider, Identity, ApiKey
+from sentry.utils import json
+from sentry.utils.http import absolute_uri
+
+from .utils import build_attachment, logger
+
+LINK_IDENTITY_MESSAGE = "Looks like you haven't linked your Sentry account with your Slack identity yet! <{associate_url}|Link your identity now> to perform actions in Sentry through Slack."
+
+RESOLVE_SELECTOR = {
+ 'label': 'Resolve issue',
+ 'type': 'select',
+ 'name': 'resolve_type',
+ 'placeholder': 'Select the resolution target',
+ 'value': 'resolved',
+ 'options': [
+ {
+ 'label': 'Immediately',
+ 'value': 'resolved'
+ },
+ {
+ 'label': 'In the next release',
+ 'value': 'resolved:inNextRelease'
+ },
+ {
+ 'label': 'In the current release',
+ 'value': 'resolved:inCurrentRelease'
+ },
+ ],
+}
+
+
+class SlackActionEndpoint(Endpoint):
+ authentication_classes = ()
+ permission_classes = ()
+
+ def on_assign(self, request, identity, group, action):
+ assignee = action['selected_options'][0]['value']
+
+ if assignee == 'none':
+ assignee = None
+
+ self.update_group(group, identity, {'assignedTo': assignee})
+
+ def on_status(self, request, identity, group, action, data, integration):
+ status = action['value']
+
+ status_data = status.split(':', 1)
+ status = {'status': status_data[0]}
+
+ resolve_type = status_data[-1]
+
+ if resolve_type == 'inNextRelease':
+ status.update({'statusDetails': {'inNextRelease': True}})
+ elif resolve_type == 'inCurrentRelease':
+ status.update({'statusDetails': {'inRelease': 'latest'}})
+
+ self.update_group(group, identity, status)
+
+ def update_group(self, group, identity, data):
+ event_write_key = ApiKey(
+ organization=group.project.organization,
+ scope_list=['event:write'],
+ )
+
+ return client.put(
+ path='/projects/{}/{}/issues/'.format(
+ group.project.organization.slug,
+ group.project.slug,
+ ),
+ params={'id': group.id},
+ data=data,
+ user=identity.user,
+ auth=event_write_key,
+ )
+
+ def open_resolve_dialog(self, data, group, integration):
+ # XXX(epurkhiser): In order to update the original message we have to
+ # keep track of the response_url in the callback_id. Definitely hacky,
+ # but seems like there's no other solutions [1]:
+ #
+ # [1]: https://stackoverflow.com/questions/46629852/update-a-bot-message-after-responding-to-a-slack-dialog#comment80795670_46629852
+ callback_id = json.dumps({
+ 'issue': group.id,
+ 'orig_response_url': data['response_url'],
+ 'is_message': self.is_message(data),
+ })
+
+ dialog = {
+ 'callback_id': callback_id,
+ 'title': u'Resolve {}'.format(group.qualified_short_id),
+ 'submit_label': 'Resolve',
+ 'elements': [RESOLVE_SELECTOR],
+ }
+
+ payload = {
+ 'dialog': json.dumps(dialog),
+ 'trigger_id': data['trigger_id'],
+ 'token': integration.metadata['bot_access_token'],
+ }
+
+ session = http.build_session()
+ req = session.post('https://slack.com/api/dialog.open', data=payload)
+ resp = req.json()
+ if not resp.get('ok'):
+ logger.error('slack.action.response-error', extra={
+ 'error': resp.get('error'),
+ })
+
+ def construct_reply(self, attachment, is_message=False):
+ # XXX(epurkhiser): Slack is inconsistent about it's expected responses
+ # for interactive action requests.
+ #
+ # * For _unfurled_ action responses, slack expects the entire
+ # attachment body used to replace the unfurled attachment to be at
+ # the top level of the json response body.
+ #
+ # * For _bot posted message_ action responses, slack expects the
+ # attachment body used to replace the attachment to be within an
+ # `attachments` array.
+ if is_message:
+ attachment = {'attachments': [attachment]}
+
+ return attachment
+
+ def is_message(self, data):
+ # XXX(epurkhsier): Used in coordination with construct_reply. Bot
+ # posted messages will not have the type at all.
+ return data.get('original_message', {}).get('type') == 'message'
+
+ def post(self, request):
+ logging_data = {}
+
+ try:
+ data = request.DATA
+ except (ValueError, TypeError):
+ logger.error('slack.action.invalid-json', extra=logging_data, exc_info=True)
+ return self.respond(status=400)
+
+ try:
+ data = json.loads(data['payload'])
+ except (KeyError, IndexError, TypeError, ValueError):
+ logger.error('slack.action.invalid-payload', extra=logging_data, exc_info=True)
+ return self.respond(status=400)
+
+ event_id = data.get('event_id')
+ team_id = data.get('team', {}).get('id')
+ channel_id = data.get('channel', {}).get('id')
+ user_id = data.get('user', {}).get('id')
+ callback_id = data.get('callback_id')
+
+ logging_data.update({
+ 'slack_team_id': team_id,
+ 'slack_channel_id': channel_id,
+ 'slack_user_id': user_id,
+ 'slack_event_id': event_id,
+ 'slack_callback_id': callback_id,
+ })
+
+ token = data.get('token')
+ if token != options.get('slack.verification-token'):
+ logger.error('slack.action.invalid-token', extra=logging_data)
+ return self.respond(status=401)
+
+ logger.info('slack.action', extra=logging_data)
+
+ try:
+ integration = Integration.objects.get(
+ provider='slack',
+ external_id=team_id,
+ )
+ except Integration.DoesNotExist:
+ logger.error('slack.action.invalid-team-id', extra=logging_data)
+ return self.respond(status=403)
+
+ logging_data['integration_id'] = integration.id
+
+ callback_data = json.loads(callback_id)
+
+ # Determine the issue group action is being taken on
+ group_id = callback_data['issue']
+
+ # Actions list may be empty when receiving a dialog response
+ action_list = data.get('actions', [])
+
+ try:
+ group = Group.objects.get(
+ project__in=Project.objects.filter(
+ organization__in=integration.organizations.all(),
+ ),
+ id=group_id,
+ )
+ except Group.DoesNotExist:
+ logger.error('slack.action.invalid-issue', extra=logging_data)
+ return self.respond(status=403)
+
+ # Determine the acting user by slack identity
+ try:
+ identity = Identity.objects.get(
+ external_id=user_id,
+ idp=IdentityProvider.objects.get(organization=group.organization),
+ )
+ except Identity.DoesNotExist:
+ associate_url = absolute_uri(reverse('sentry-account-associate-identity', kwargs={
+ 'organization_slug': group.organization.slug,
+ 'provider_key': 'slack',
+ }))
+
+ return self.respond({
+ 'response_type': 'ephemeral',
+ 'replace_original': False,
+ 'text': LINK_IDENTITY_MESSAGE.format(associate_url=associate_url)
+ })
+
+ # Handle status dialog submission
+ if data['type'] == 'dialog_submission' and 'resolve_type' in data['submission']:
+ # Masquerade a status action
+ action = {
+ 'name': 'status',
+ 'value': data['submission']['resolve_type'],
+ }
+
+ self.on_status(request, identity, group, action, data, integration)
+ group = Group.objects.get(id=group.id)
+
+ attachment = build_attachment(group, identity=identity, actions=[action])
+
+ body = self.construct_reply(attachment, is_message=callback_data['is_message'])
+
+ # use the original response_url to update the link attachment
+ session = http.build_session()
+ req = session.post(callback_data['orig_response_url'], json=body)
+ resp = req.json()
+ if not resp.get('ok'):
+ logger.error('slack.action.response-error', extra={
+ 'error': resp.get('error'),
+ })
+
+ return self.respond()
+
+ # Usually we'll want to respond with the updated attachment including
+ # the list of actions taken. However, when opening a dialog we do not
+ # have anything to update the message with and will use the
+ # response_url later to update it.
+ defer_attachment_update = False
+
+ # Handle interaction actions
+ try:
+ for action in action_list:
+ if action['name'] == 'status':
+ self.on_status(request, identity, group, action, data, integration)
+ elif action['name'] == 'assign':
+ self.on_assign(request, identity, group, action)
+ elif action['name'] == 'resolve_dialog':
+ self.open_resolve_dialog(data, group, integration)
+ defer_attachment_update = True
+ except client.ApiError as e:
+ return self.respond({
+ 'response_type': 'ephemeral',
+ 'replace_original': False,
+ 'text': u'Action failed: {}'.format(e.body['detail']),
+ })
+
+ if defer_attachment_update:
+ return self.respond()
+
+ # Reload group as it may have been mutated by the action
+ group = Group.objects.get(id=group.id)
+
+ attachment = build_attachment(group, identity=identity, actions=action_list)
+ body = self.construct_reply(attachment, is_message=self.is_message(data))
+
+ return self.respond(body)
diff --git a/src/sentry/integrations/slack/event_endpoint.py b/src/sentry/integrations/slack/event_endpoint.py
index 56811e55b0a65c..2544d5339c788a 100644
--- a/src/sentry/integrations/slack/event_endpoint.py
+++ b/src/sentry/integrations/slack/event_endpoint.py
@@ -1,27 +1,18 @@
from __future__ import absolute_import
-import logging
import json
import re
import six
-from six.moves.urllib.parse import parse_qs, urlencode, urlparse, urlunparse
-
from sentry import http, options
from sentry.api.base import Endpoint
from sentry.models import Group, Integration, Project
-logger = logging.getLogger('sentry.integrations.slack')
-
-_link_regexp = re.compile(r'^https?\://[^/]+/[^/]+/[^/]+/issues/(\d+)/')
+from .utils import build_attachment, logger
-LEVEL_TO_COLOR = {
- 'debug': 'cfd3da',
- 'info': '2788ce',
- 'warning': 'f18500',
- 'error': 'f43f20',
- 'fatal': 'd20f2a',
-}
+# XXX(dcramer): this could be more tightly bound to our configured domain,
+# but slack limits what we can unfurl anyways so its probably safe
+_link_regexp = re.compile(r'^https?\://[^/]+/[^/]+/[^/]+/issues/(\d+)')
# XXX(dcramer): a lot of this is copied from sentry-plugins right now, and will
@@ -39,22 +30,6 @@ def _parse_issue_id_from_url(self, link):
except (TypeError, ValueError):
return
- def _attachment_for(self, group):
- return {
- 'fallback': '[{}] {}'.format(group.project.slug, group.title),
- 'title': group.title,
- 'title_link': self._add_notification_referrer_param(group.get_absolute_url()),
- }
-
- def _add_notification_referrer_param(self, url):
- parsed_url = urlparse(url)
- query = parse_qs(parsed_url.query)
- query['referrer'] = 'slack'
-
- url_list = list(parsed_url)
- url_list[4] = urlencode(query, doseq=True)
- return urlunparse(url_list)
-
def on_url_verification(self, request, data):
return self.respond({
'challenge': data['challenge'],
@@ -87,7 +62,7 @@ def on_link_shared(self, request, integration, token, data):
'channel': data['channel'],
'ts': data['message_ts'],
'unfurls': json.dumps({
- v: self._attachment_for(results[k])
+ v: build_attachment(results[k])
for k, v in six.iteritems(issue_map)
if k in results
}),
@@ -124,9 +99,9 @@ def post(self, request):
# authed_users = data.get('authed_users')
logging_data.update({
- 'team_id': team_id,
- 'api_app_id': api_app_id,
- 'event_id': event_id,
+ 'slack_team_id': team_id,
+ 'slack_api_app_id': api_app_id,
+ 'slack_event_id': event_id,
})
token = data.get('token')
@@ -159,7 +134,7 @@ def post(self, request):
logger.error('slack.event.invalid-event-type', extra=logging_data)
return self.respond(status=400)
- logging_data['event_type'] = event_type
+ logging_data['slack_event_type'] = event_type
if event_type == 'link_shared':
resp = self.on_link_shared(request, integration, token, event_data)
else:
diff --git a/src/sentry/integrations/slack/integration.py b/src/sentry/integrations/slack/integration.py
index dafd67b10d0775..e4707c4fd24dd4 100644
--- a/src/sentry/integrations/slack/integration.py
+++ b/src/sentry/integrations/slack/integration.py
@@ -1,69 +1,61 @@
from __future__ import absolute_import
-from sentry import options
-from sentry.integrations import OAuth2Integration
+from sentry.integrations import Integration
+from sentry.utils.pipeline import NestedPipelineView
+from sentry.identity.pipeline import IdentityProviderPipeline
+from sentry.utils.http import absolute_uri
-options.register('slack.client-id')
-options.register('slack.client-secret')
-options.register('slack.verification-token')
-
-class SlackIntegration(OAuth2Integration):
- id = 'slack'
+class SlackIntegration(Integration):
+ key = 'slack'
name = 'Slack'
- oauth_access_token_url = 'https://slack.com/api/oauth.access'
- oauth_authorize_url = 'https://slack.com/oauth/authorize'
- oauth_scopes = tuple(sorted((
+ identity_oauth_scopes = frozenset([
'bot',
+ 'channels:read',
'chat:write:bot',
'commands',
'links:read',
'links:write',
'team:read',
- )))
+ ])
- def get_oauth_client_id(self):
- return options.get('slack.client-id')
+ def get_pipeline_views(self):
+ identity_pipeline_config = {
+ 'oauth_scopes': self.identity_oauth_scopes,
+ 'redirect_url': absolute_uri('/extensions/slack/setup/'),
+ }
- def get_oauth_client_secret(self):
- return options.get('slack.client-secret')
+ identity_pipeline_view = NestedPipelineView(
+ bind_key='identity',
+ provider_key='slack',
+ pipeline_cls=IdentityProviderPipeline,
+ config=identity_pipeline_config,
+ )
- def get_config(self):
- return [{
- 'name': 'unfurl_urls',
- 'label': 'Unfurl URLs',
- 'type': 'bool',
- 'help': 'Unfurl any URLs which reference a Sentry issue.',
- }]
+ return [identity_pipeline_view]
def build_integration(self, state):
- data = state['data']
+ data = state['identity']['data']
assert data['ok']
+
+ scopes = sorted(data['scope'].split(','))
+
return {
- 'external_id': data['team_id'],
'name': data['team_name'],
- # TODO(dcramer): we should probably store an Identity for the bot,
- # and just skip associating them with a user?
+ 'external_id': data['team_id'],
'metadata': {
+ 'access_token': data['access_token'],
'bot_access_token': data['bot']['bot_access_token'],
'bot_user_id': data['bot']['bot_user_id'],
- # XXX: should this be stored with OrganizationIntegration?
- # is there any concern of access?
- 'access_token': data['access_token'],
- 'scopes': sorted(data['scope'].split(',')),
+ 'scopes': scopes,
},
- 'identity': self.build_identity(state)
- }
-
- def build_identity(self, state):
- data = state['data']
- return {
- 'type': 'slack',
- 'instance': 'slack.com',
- 'external_id': data['user_id'],
- 'scopes': sorted(data['scope'].split(',')),
- 'data': {
- 'access_token': data['access_token'],
+ 'user_identity': {
+ 'type': 'slack',
+ 'external_id': data['user_id'],
+ 'scopes': scopes,
+ 'data': {
+ 'access_token': data['access_token'],
+ },
},
}
diff --git a/src/sentry/integrations/slack/notify_action.py b/src/sentry/integrations/slack/notify_action.py
new file mode 100644
index 00000000000000..8ee7c281555792
--- /dev/null
+++ b/src/sentry/integrations/slack/notify_action.py
@@ -0,0 +1,141 @@
+from __future__ import absolute_import
+
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from sentry import http
+from sentry.rules.actions.base import EventAction
+from sentry.utils import metrics, json
+from sentry.models import Integration
+
+from .utils import build_attachment
+
+
+class SlackNotifyServiceForm(forms.Form):
+ team = forms.ChoiceField(choices=(), widget=forms.Select(
+ attrs={'style': 'width:150px'},
+ ))
+ channel = forms.CharField(widget=forms.TextInput(
+ attrs={'placeholder': 'i.e #critical-errors'},
+ ))
+
+ def __init__(self, *args, **kwargs):
+ # NOTE: Team maps directly to the integration ID
+ team_list = [(i.id, i.name) for i in kwargs.pop('integrations')]
+ self.channel_transformer = kwargs.pop('channel_transformer')
+
+ super(SlackNotifyServiceForm, self).__init__(*args, **kwargs)
+
+ self.fields['team'].choices = team_list
+ self.fields['team'].widget.choices = self.fields['team'].choices
+
+ def clean_channel(self):
+ team = self.cleaned_data.get('team')
+ channel = self.cleaned_data.get('channel', '').lstrip('#')
+
+ channel_id = self.channel_transformer(team, channel)
+
+ if channel_id is None and team is not None:
+ params = {
+ 'channel': channel,
+ 'team': dict(self.fields['team'].choices).get(int(team)),
+ }
+
+ raise forms.ValidationError(
+ _('The #%(channel)s channel does not exist in the %(team)s Slack team.'),
+ code='invalid',
+ params=params,
+ )
+
+ return channel
+
+
+class SlackNotifyServiceAction(EventAction):
+ form_cls = SlackNotifyServiceForm
+ label = u'Send a notification to the Slack {team} team in {channel}'
+
+ def is_enabled(self):
+ return self.get_integrations().exists()
+
+ def after(self, event, state):
+ integration_id = self.get_option('team')
+ channel = self.get_option('channel')
+
+ integration = Integration.objects.get(
+ provider='slack',
+ organizations=self.project.organization,
+ id=integration_id
+ )
+
+ def send_notification(event, futures):
+ attachment = build_attachment(event.group, event=event)
+
+ payload = {
+ 'token': integration.metadata['access_token'],
+ 'channel': channel,
+ 'attachments': json.dumps([attachment]),
+ }
+
+ session = http.build_session()
+ resp = session.post('https://slack.com/api/chat.postMessage', data=payload)
+ resp.raise_for_status()
+ resp = resp.json()
+ if not resp.get('ok'):
+ self.logger.info('rule.fail.slack_post', extra={'error': resp.get('error')})
+
+ metrics.incr('notifications.sent', instance='slack.notification')
+ yield self.future(send_notification)
+
+ def render_label(self):
+ try:
+ integration_name = Integration.objects.get(
+ provider='slack',
+ organizations=self.project.organization,
+ id=self.data['team'],
+ ).name
+ except Integration.DoesNotExist:
+ integration_name = '[removed]'
+
+ return self.label.format(
+ team=integration_name,
+ channel='#' + self.data['channel'],
+ )
+
+ def get_integrations(self):
+ return Integration.objects.filter(
+ provider='slack',
+ organizations=self.project.organization,
+ )
+
+ def get_channel_id(self, integration_id, channel_name):
+ try:
+ integration = Integration.objects.get(
+ provider='slack',
+ organizations=self.project.organization,
+ id=integration_id,
+ )
+ except Integration.DoesNotExist:
+ return None
+
+ payload = {
+ 'token': integration.metadata['access_token'],
+ 'exclude_archived': False,
+ 'exclude_members': True,
+ }
+
+ session = http.build_session()
+ resp = session.get('https://slack.com/api/channels.list', params=payload)
+ resp.raise_for_status()
+ resp = resp.json()
+ if not resp.get('ok'):
+ self.logger.info('rule.slack.channel_list_failed', extra={'error': resp.get('error')})
+ return None
+
+ return {c['name']: c['id'] for c in resp['channels']}.get(channel_name)
+
+ def get_form_instance(self):
+ return self.form_cls(
+ self.data,
+ integrations=self.get_integrations(),
+ channel_transformer=self.get_channel_id,
+ )
diff --git a/src/sentry/integrations/slack/urls.py b/src/sentry/integrations/slack/urls.py
index 4f4a37105e7c42..d9efc3395a0518 100644
--- a/src/sentry/integrations/slack/urls.py
+++ b/src/sentry/integrations/slack/urls.py
@@ -2,10 +2,12 @@
from django.conf.urls import patterns, url
+from .action_endpoint import SlackActionEndpoint
from .event_endpoint import SlackEventEndpoint
urlpatterns = patterns(
'',
+ url(r'^action/$', SlackActionEndpoint.as_view()),
url(r'^event/$', SlackEventEndpoint.as_view()),
)
diff --git a/src/sentry/integrations/slack/utils.py b/src/sentry/integrations/slack/utils.py
new file mode 100644
index 00000000000000..349968b5676fe6
--- /dev/null
+++ b/src/sentry/integrations/slack/utils.py
@@ -0,0 +1,204 @@
+from __future__ import absolute_import
+
+import logging
+import time
+
+from django.db.models import Q
+from six.moves.urllib.parse import parse_qs, urlencode, urlparse, urlunparse
+
+from sentry.utils import json
+from sentry.utils.assets import get_asset_url
+from sentry.utils.http import absolute_uri
+from sentry.models import GroupStatus, GroupAssignee, OrganizationMember, User, Identity
+
+logger = logging.getLogger('sentry.integrations.slack')
+
+UNASSIGN_OPTION = {
+ 'text': u':negative_squared_cross_mark: Unassign Issue',
+ 'value': 'none',
+}
+
+# Attachment colors used for issues with no actions take
+NEW_ISSUE_COLOR = '#E03E2F'
+ACTIONED_ISSUE_COLOR = '#EDEEEF'
+
+
+def get_assignees(group):
+ queryset = OrganizationMember.objects.filter(
+ Q(user__is_active=True) | Q(user__isnull=True),
+ organization=group.organization,
+ teams__in=group.project.teams.all(),
+ ).select_related('user')
+
+ members = sorted(queryset, key=lambda x: x.user.get_display_name() if x.user_id else x.email)
+ members = filter(lambda m: m.user_id is not None, members)
+
+ return [{'text': x.user.get_display_name(), 'value': x.user.username} for x in members]
+
+
+def add_notification_referrer_param(url, provider):
+ parsed_url = urlparse(url)
+ query = parse_qs(parsed_url.query)
+ query['referrer'] = provider
+ url_list = list(parsed_url)
+ url_list[4] = urlencode(query, doseq=True)
+ return urlunparse(url_list)
+
+
+def build_attachment_title(group, event=None):
+ ev_metadata = group.get_event_metadata()
+ ev_type = group.get_event_type()
+ if ev_type == 'error':
+ if group.culprit:
+ return u'{} - {}'.format(ev_metadata['type'][:40], group.culprit)
+ return ev_metadata['type']
+ elif ev_type == 'csp':
+ return u'{} - {}'.format(ev_metadata['directive'], ev_metadata['uri'])
+ else:
+ if group.culprit:
+ return u'{} - {}'.format(group.title[:40], group.culprit)
+ return group.title
+
+
+def build_attachment_text(group, event=None):
+ ev_metadata = group.get_event_metadata()
+ ev_type = group.get_event_type()
+ if ev_type == 'error':
+ return ev_metadata['value']
+ else:
+ return None
+
+
+def build_assigned_text(identity, assignee):
+ if assignee == 'none':
+ return u'*Issue unassigned by <@{user_id}>*'.format(
+ user_id=identity.external_id,
+ )
+
+ try:
+ assignee_user = User.objects.get(username=assignee)
+ except User.DoesNotExist:
+ return
+
+ try:
+ assignee_ident = Identity.objects.get(user=assignee_user)
+ assignee_text = u'<@{}>'.format(assignee_ident.external_id)
+ except Identity.DoesNotExist:
+ assignee_text = assignee_user.get_display_name()
+
+ return u'*Issue assigned to {assignee_text} by <@{user_id}>*'.format(
+ assignee_text=assignee_text,
+ user_id=identity.external_id,
+ )
+
+
+def build_action_text(identity, action):
+ if action['name'] == 'assign':
+ return build_assigned_text(identity, action['selected_options'][0]['value'])
+
+ statuses = {
+ 'resolved': 'resolved',
+ 'ignored': 'ignored',
+ 'unresolved': 're-opened',
+ }
+
+ # Resolve actions have additional 'parameters' after ':'
+ status = action['value'].split(':', 1)[0]
+
+ # Action has no valid action text, ignore
+ if status not in statuses:
+ return
+
+ return u'*Issue {status} by <@{user_id}>*'.format(
+ status=statuses[status],
+ user_id=identity.external_id,
+ )
+
+
+def build_attachment(group, event=None, identity=None, actions=None):
+ # XXX(dcramer): options are limited to 100 choices, even when nested
+ status = group.get_status()
+ assignees = get_assignees(group)
+
+ logo_url = absolute_uri(get_asset_url('sentry', 'images/sentry-email-avatar.png'))
+ color = NEW_ISSUE_COLOR
+
+ text = build_attachment_text(group, event) or ''
+
+ if actions is None:
+ actions = []
+
+ try:
+ assignee = GroupAssignee.objects.get(group=group).user
+ assignee = {
+ 'text': assignee.get_display_name(),
+ 'value': assignee.username,
+ }
+
+ # Add unassign option to the top of the list
+ assignees.insert(0, UNASSIGN_OPTION)
+ except GroupAssignee.DoesNotExist:
+ assignee = None
+
+ resolve_button = {
+ 'name': 'resolve_dialog',
+ 'value': 'resolve_dialog',
+ 'type': 'button',
+ 'text': 'Resolve',
+ }
+
+ ignore_button = {
+ 'name': 'status',
+ 'value': 'ignored',
+ 'type': 'button',
+ 'text': 'Ignore',
+ }
+
+ if status == GroupStatus.RESOLVED:
+ resolve_button.update({
+ 'name': 'status',
+ 'text': 'Unresolve',
+ 'value': 'unresolved',
+ })
+
+ if status == GroupStatus.IGNORED:
+ ignore_button.update({
+ 'text': 'Stop Ignoring',
+ 'value': 'unresolved',
+ })
+
+ payload_actions = [
+ resolve_button,
+ ignore_button,
+ {
+ 'name': 'assign',
+ 'text': 'Select Assignee ..',
+ 'type': 'select',
+ 'options': assignees,
+ 'selected_options': [assignee],
+ },
+ ]
+
+ if actions:
+ action_texts = filter(None, [build_action_text(identity, a) for a in actions])
+ text += '\n' + '\n'.join(action_texts)
+
+ color = ACTIONED_ISSUE_COLOR
+ payload_actions = []
+
+ return {
+ 'fallback': u'[{}] {}'.format(group.project.slug, group.title),
+ 'title': build_attachment_title(group, event),
+ 'title_link': add_notification_referrer_param(group.get_absolute_url(), 'slack'),
+ 'text': text,
+ 'mrkdwn_in': ['text'],
+ 'callback_id': json.dumps({'issue': group.id}),
+ 'footer_icon': logo_url,
+ 'footer': u'{} / {}'.format(
+ group.organization.slug,
+ group.project.slug,
+ ),
+ 'ts': int(time.mktime(group.last_seen.timetuple())),
+ 'color': color,
+ 'actions': payload_actions,
+ }
diff --git a/src/sentry/integrations/view.py b/src/sentry/integrations/view.py
deleted file mode 100644
index 69c3a913027dd7..00000000000000
--- a/src/sentry/integrations/view.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from __future__ import absolute_import, print_function
-
-__all__ = ['PipelineView']
-
-from django.http import HttpResponseRedirect
-from django.views.generic import View
-
-
-class PipelineView(View):
- def redirect(self, url):
- return HttpResponseRedirect(url)
diff --git a/src/sentry/interfaces/base.py b/src/sentry/interfaces/base.py
index 4c49e02ea5b342..413937cf2dabad 100644
--- a/src/sentry/interfaces/base.py
+++ b/src/sentry/interfaces/base.py
@@ -1,23 +1,14 @@
from __future__ import absolute_import
import six
+from collections import OrderedDict
from django.conf import settings
from django.utils.translation import ugettext as _
from sentry.utils.html import escape
from sentry.utils.imports import import_string
-
-
-def iter_interfaces():
- rv = {}
-
- for name, import_path in six.iteritems(settings.SENTRY_INTERFACES):
- rv.setdefault(import_path, []).append(name)
-
- for import_path, keys in six.iteritems(rv):
- iface = import_string(import_path)
- yield iface, keys
+from sentry.utils.safe import safe_execute
def get_interface(name):
@@ -34,6 +25,27 @@ def get_interface(name):
return interface
+def get_interfaces(data):
+ result = []
+ for key, data in six.iteritems(data):
+ try:
+ cls = get_interface(key)
+ except ValueError:
+ continue
+
+ value = safe_execute(
+ cls.to_python, data, _with_transaction=False
+ )
+ if not value:
+ continue
+
+ result.append((key, value))
+
+ return OrderedDict(
+ (k, v) for k, v in sorted(result, key=lambda x: x[1].get_score(), reverse=True)
+ )
+
+
class InterfaceValidationError(Exception):
pass
@@ -76,7 +88,7 @@ def __setattr__(self, name, value):
@classmethod
def to_python(cls, data):
- return cls(data)
+ return cls(**data)
def get_api_context(self, is_public=False):
return self.to_json()
diff --git a/src/sentry/interfaces/csp.py b/src/sentry/interfaces/csp.py
deleted file mode 100644
index d65f3a849c3234..00000000000000
--- a/src/sentry/interfaces/csp.py
+++ /dev/null
@@ -1,232 +0,0 @@
-"""
-sentry.interfaces.csp
-~~~~~~~~~~~~~~~~~~~~~
-
-:copyright: (c) 2010-2015 by the Sentry Team, see AUTHORS for more details.
-:license: BSD, see LICENSE for more details.
-"""
-
-from __future__ import absolute_import
-
-__all__ = ('Csp', )
-
-from six.moves.urllib.parse import urlsplit, urlunsplit
-
-from sentry.interfaces.base import Interface
-from sentry.utils import json
-from sentry.utils.cache import memoize
-from sentry.utils.safe import trim
-from sentry.web.helpers import render_to_string
-
-# Sourced from https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives
-REPORT_KEYS = frozenset(
- (
- 'blocked_uri',
- 'document_uri',
- 'effective_directive',
- 'original_policy',
- 'referrer',
- 'status_code',
- 'violated_directive',
- 'source_file',
- 'line_number',
- 'column_number',
-
- # FireFox specific keys
- 'script_sample',
- )
-)
-
-KEYWORDS = frozenset(("'none'", "'self'", "'unsafe-inline'", "'unsafe-eval'", ))
-
-ALL_SCHEMES = ('data:', 'mediastream:', 'blob:', 'filesystem:', 'http:', 'https:', 'file:', )
-
-SELF = "'self'"
-
-DIRECTIVE_TO_MESSAGES = {
- # 'base-uri': '',
- 'child-src': (u"Blocked 'child' from '{uri}'", "Blocked inline 'child'"),
- 'connect-src': (u"Blocked 'connect' from '{uri}'", "Blocked inline 'connect'"),
- # 'default-src': '',
- 'font-src': (u"Blocked 'font' from '{uri}'", "Blocked inline 'font'"),
- 'form-action': (u"Blocked 'form' action to '{uri}'", ), # no inline option
- # 'frame-ancestors': '',
- 'img-src': (u"Blocked 'image' from '{uri}'", "Blocked inline 'image'"),
- 'manifest-src': (u"Blocked 'manifest' from '{uri}'", "Blocked inline 'manifest'"),
- 'media-src': (u"Blocked 'media' from '{uri}'", "Blocked inline 'media'"),
- 'object-src': (u"Blocked 'object' from '{uri}'", "Blocked inline 'object'"),
- # 'plugin-types': '',
- # 'referrer': '',
- # 'reflected-xss': '',
- 'script-src': (u"Blocked 'script' from '{uri}'", "Blocked unsafe (eval() or inline) 'script'"),
- 'style-src': (u"Blocked 'style' from '{uri}'", "Blocked inline 'style'"),
- # 'upgrade-insecure-requests': '',
-}
-
-DEFAULT_MESSAGE = ('Blocked {directive!r} from {uri!r}', 'Blocked inline {directive!r}')
-
-
-class Csp(Interface):
- """
- A CSP violation report.
-
- See also: http://www.w3.org/TR/CSP/#violation-reports
-
- >>> {
- >>> "document_uri": "http://example.com/",
- >>> "violated_directive": "style-src cdn.example.com",
- >>> "blocked_uri": "http://example.com/style.css",
- >>> "effective_directive": "style-src",
- >>> }
- """
-
- score = 1300
- display_score = 1300
-
- @classmethod
- def to_python(cls, data):
- kwargs = {k: trim(data.get(k, None), 1024) for k in REPORT_KEYS}
-
- # Anything resulting from an "inline" whatever violation is either sent
- # as 'self', or left off. In the case if it missing, we want to noramalize.
- if not kwargs['blocked_uri']:
- kwargs['blocked_uri'] = 'self'
-
- return cls(**kwargs)
-
- def get_hash(self):
- directive = self.effective_directive
- uri = self._normalized_blocked_uri
-
- # We want to distinguish between the different script-src
- # violations that happen in
- if _is_unsafe_script(directive, uri) and self.violated_directive:
- if "'unsafe-inline" in self.violated_directive:
- uri = "'unsafe-eval'"
- elif "'unsafe-eval'" in self.violated_directive:
- uri = "'unsafe-inline"
-
- return [directive, uri]
-
- def get_message(self):
- directive = self.effective_directive
- uri = self._normalized_blocked_uri
-
- index = 1 if uri == SELF else 0
-
- tmpl = None
-
- # We want to special case script-src because they have
- # unsafe-inline and unsafe-eval, but the report is ambiguous.
- # so we want to attempt to guess which it was
- if _is_unsafe_script(directive, uri) and self.violated_directive:
- if "'unsafe-inline'" in self.violated_directive:
- tmpl = "Blocked unsafe eval() 'script'"
- elif "'unsafe-eval'" in self.violated_directive:
- tmpl = "Blocked unsafe inline 'script'"
-
- if tmpl is None:
- try:
- tmpl = DIRECTIVE_TO_MESSAGES[directive][index]
- except (KeyError, IndexError):
- tmpl = DEFAULT_MESSAGE[index]
-
- return tmpl.format(directive=directive, uri=uri)
-
- def get_culprit(self):
- return self._normalize_directive(self.violated_directive)
-
- def get_tags(self):
- return (
- ('effective-directive', self.effective_directive),
- ('blocked-uri', self.sanitized_blocked_uri()),
- )
-
- def sanitized_blocked_uri(self):
- # HACK: This is 100% to work around Stripe urls
- # that will casually put extremely sensitive information
- # in querystrings. The real solution is to apply
- # data scrubbing to all tags generically
- uri = self.blocked_uri
- if uri[:23] == 'https://api.stripe.com/':
- return urlunsplit(urlsplit(uri)[:3] + (None, None))
- return uri
-
- @memoize
- def _normalized_blocked_uri(self):
- return _normalize_uri(self.blocked_uri)
-
- @memoize
- def _normalized_document_uri(self):
- return _normalize_uri(self.document_uri)
-
- def _normalize_directive(self, directive):
- bits = [d for d in directive.split(' ') if d]
- return ' '.join([bits[0]] + list(map(self._normalize_value, bits[1:])))
-
- def _normalize_value(self, value):
- # > If no scheme is specified, the same scheme as the one used to
- # > access the protected document is assumed.
- # Source: https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives
- if value in KEYWORDS:
- return value
-
- # normalize a value down to 'self' if it matches the origin of document-uri
- # FireFox transforms a 'self' value into the spelled out origin, so we
- # want to reverse this and bring it back
- if value.startswith(ALL_SCHEMES):
- if self._normalized_document_uri == _normalize_uri(value):
- return SELF
- # Their rule had an explicit scheme, so let's respect that
- return value
-
- # value doesn't have a scheme, but let's see if their
- # hostnames match at least, if so, they're the same
- if value == self._normalized_document_uri:
- return SELF
-
- # Now we need to stitch on a scheme to the value
- scheme = self.document_uri.split(':', 1)[0]
- # But let's not stitch on the boring values
- if scheme in ('http', 'https'):
- return value
- return _unsplit(scheme, value)
-
- def get_title(self):
- return 'CSP Report'
-
- def to_string(self, is_public=False, **kwargs):
- return json.dumps({'csp-report': self.get_api_context()}, indent=2)
-
- def to_email_html(self, event, **kwargs):
- return render_to_string(
- 'sentry/partial/interfaces/csp_email.html', {'data': self.get_api_context()}
- )
-
- def get_path(self):
- return 'sentry.interfaces.Csp'
-
-
-def _is_unsafe_script(directive, uri):
- return directive == 'script-src' and uri == SELF
-
-
-def _normalize_uri(value):
- if value in ('self', "'self'"):
- return SELF
-
- # A lot of these values get reported as literally
- # just the scheme. So a value like 'data' or 'blob', which
- # are valid schemes, just not a uri. So we want to
- # normalize it into a uri.
- if ':' not in value:
- scheme, hostname = value, ''
- else:
- scheme, hostname = urlsplit(value)[:2]
- if scheme in ('http', 'https'):
- return hostname
- return _unsplit(scheme, hostname)
-
-
-def _unsplit(scheme, hostname):
- return urlunsplit((scheme, hostname, '', None, None))
diff --git a/src/sentry/interfaces/debug_meta.py b/src/sentry/interfaces/debug_meta.py
index ca2067e2ebec46..209b01aeb6fa36 100644
--- a/src/sentry/interfaces/debug_meta.py
+++ b/src/sentry/interfaces/debug_meta.py
@@ -6,7 +6,8 @@
__all__ = ('DebugMeta', )
from sentry.interfaces.base import Interface, InterfaceValidationError
-from sentry.utils.native import parse_addr
+
+from symbolic import parse_addr
image_types = {}
@@ -26,12 +27,13 @@ def _addr(x):
try:
apple_image = {
- 'cpu_type': image['cpu_type'],
- 'cpu_subtype': image['cpu_subtype'],
- 'image_addr': _addr(image['image_addr']),
+ 'arch': image.get('arch'),
+ 'cpu_type': image.get('cpu_type'),
+ 'cpu_subtype': image.get('cpu_subtype'),
+ 'image_addr': _addr(image.get('image_addr')),
'image_size': image['image_size'],
'image_vmaddr': _addr(image.get('image_vmaddr') or 0),
- 'name': image['name'],
+ 'name': image.get('name'),
'uuid': six.text_type(uuid.UUID(image['uuid']))
}
if image.get('major_version') is not None:
diff --git a/src/sentry/interfaces/device.py b/src/sentry/interfaces/device.py
index 7cbad6f0354f3b..de2b2742f41659 100644
--- a/src/sentry/interfaces/device.py
+++ b/src/sentry/interfaces/device.py
@@ -3,6 +3,7 @@
__all__ = ('Device', )
from sentry.interfaces.base import Interface, InterfaceValidationError
+from sentry.interfaces.schemas import validate_and_default_interface
from sentry.utils.safe import trim, trim_dict
@@ -17,25 +18,19 @@ class Device(Interface):
>>> "arbitrary": "data"
>>> }
"""
+ path = 'device'
@classmethod
def to_python(cls, data):
+ is_valid, errors = validate_and_default_interface(data, cls.path)
+ if not is_valid:
+ raise InterfaceValidationError("Invalid device")
+
data = data.copy()
extra_data = data.pop('data', data)
- if not isinstance(extra_data, dict):
- extra_data = {}
-
- try:
- name = trim(data.pop('name'), 64)
- except KeyError:
- raise InterfaceValidationError("Missing or invalid value for 'name'")
-
- try:
- version = trim(data.pop('version'), 64)
- except KeyError:
- raise InterfaceValidationError("Missing or invalid value for 'version'")
-
+ name = trim(data.pop('name'), 64)
+ version = trim(data.pop('version'), 64)
build = trim(data.pop('build', None), 64)
kwargs = {
diff --git a/src/sentry/interfaces/exception.py b/src/sentry/interfaces/exception.py
index b3e03210adaff2..90dacd8625a6e2 100644
--- a/src/sentry/interfaces/exception.py
+++ b/src/sentry/interfaces/exception.py
@@ -10,15 +10,19 @@
__all__ = ('Exception', )
+import re
import six
from django.conf import settings
from sentry.interfaces.base import Interface, InterfaceValidationError
+from sentry.interfaces.schemas import validate_and_default_interface
from sentry.interfaces.stacktrace import Stacktrace, slim_frame_data
from sentry.utils import json
from sentry.utils.safe import trim
+_type_value_re = re.compile('^(\w+):(.*)$')
+
class SingleException(Interface):
"""
@@ -40,9 +44,14 @@ class SingleException(Interface):
>>> }
"""
score = 2000
+ path = 'sentry.interfaces.Exception'
@classmethod
def to_python(cls, data, slim_frames=True):
+ is_valid, errors = validate_and_default_interface(data, cls.path)
+ if not is_valid:
+ raise InterfaceValidationError("Invalid exception")
+
if not (data.get('type') or data.get('value')):
raise InterfaceValidationError("No 'type' or 'value' present")
@@ -63,19 +72,19 @@ def to_python(cls, data, slim_frames=True):
type = data.get('type')
value = data.get('value')
- if not type and ':' in value.split(' ', 1)[0]:
- type, value = value.split(':', 1)
- # in case of TypeError: foo (no space)
- value = value.strip()
-
- if value is not None and not isinstance(value, six.string_types):
+ if isinstance(value, six.string_types):
+ if type is None:
+ m = _type_value_re.match(value)
+ if m:
+ type = m.group(1)
+ value = m.group(2).strip()
+ elif value is not None:
value = json.dumps(value)
+
value = trim(value, 4096)
mechanism = data.get('mechanism')
if mechanism is not None:
- if not isinstance(mechanism, dict):
- raise InterfaceValidationError('Bad value for mechanism')
mechanism = trim(data.get('mechanism'), 4096)
mechanism.setdefault('type', 'generic')
@@ -137,7 +146,7 @@ def get_alias(self):
return 'exception'
def get_path(self):
- return 'sentry.interfaces.Exception'
+ return self.path
def get_hash(self, platform=None):
output = None
diff --git a/src/sentry/interfaces/http.py b/src/sentry/interfaces/http.py
index d90866c14322e4..a1885359d1a3c7 100644
--- a/src/sentry/interfaces/http.py
+++ b/src/sentry/interfaces/http.py
@@ -18,8 +18,10 @@
from six.moves.urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
from sentry.interfaces.base import Interface, InterfaceValidationError
+from sentry.interfaces.schemas import validate_and_default_interface
from sentry.utils.safe import trim, trim_dict, trim_pairs
from sentry.utils.http import heuristic_decode
+from sentry.utils.validators import validate_ip
from sentry.web.helpers import render_to_string
# Instead of relying on a list of hardcoded methods, just loosly match
@@ -116,13 +118,15 @@ class Http(Interface):
"""
display_score = 1000
score = 800
+ path = 'sentry.interfaces.Http'
FORM_TYPE = 'application/x-www-form-urlencoded'
@classmethod
def to_python(cls, data):
- if not data.get('url'):
- raise InterfaceValidationError("No value for 'url'")
+ is_valid, errors = validate_and_default_interface(data, cls.path)
+ if not is_valid:
+ raise InterfaceValidationError("Invalid interface data")
kwargs = {}
@@ -146,7 +150,6 @@ def to_python(cls, data):
[(to_bytes(k), to_bytes(v)) for k, v in query_string.items()]
)
else:
- query_string = query_string
if query_string[0] == '?':
# remove '?' prefix
query_string = query_string[1:]
@@ -189,9 +192,17 @@ def to_python(cls, data):
if body:
body = trim(body, settings.SENTRY_MAX_HTTP_BODY_SIZE)
+ env = data.get('env', {})
+ # TODO (alex) This could also be accomplished with schema (with formats)
+ if 'REMOTE_ADDR' in env:
+ try:
+ validate_ip(env['REMOTE_ADDR'], required=False)
+ except ValueError:
+ del env['REMOTE_ADDR']
+
kwargs['inferred_content_type'] = inferred_content_type
kwargs['cookies'] = trim_pairs(format_cookies(cookies))
- kwargs['env'] = trim_dict(data.get('env') or {})
+ kwargs['env'] = trim_dict(env)
kwargs['headers'] = trim_pairs(headers)
kwargs['data'] = fix_broken_encoding(body)
kwargs['url'] = urlunsplit((scheme, netloc, path, '', ''))
@@ -200,7 +211,7 @@ def to_python(cls, data):
return cls(**kwargs)
def get_path(self):
- return 'sentry.interfaces.Http'
+ return self.path
@property
def full_url(self):
diff --git a/src/sentry/interfaces/query.py b/src/sentry/interfaces/query.py
index 5dfde28ed51956..e8282df16ec913 100644
--- a/src/sentry/interfaces/query.py
+++ b/src/sentry/interfaces/query.py
@@ -26,6 +26,8 @@ class Query(Interface):
@classmethod
def to_python(cls, data):
+ if not isinstance(data, dict):
+ raise InterfaceValidationError("Invalid interface data")
if not data.get('query'):
raise InterfaceValidationError("No 'query' value")
diff --git a/src/sentry/interfaces/schemas.py b/src/sentry/interfaces/schemas.py
new file mode 100644
index 00000000000000..a69f7bff11f87c
--- /dev/null
+++ b/src/sentry/interfaces/schemas.py
@@ -0,0 +1,698 @@
+"""
+sentry.interfaces.schemas
+~~~~~~~~~~~~~~~~~~~~~
+
+:copyright: (c) 2010-2017 by the Sentry Team, see AUTHORS for more details.
+:license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import absolute_import
+
+from functools32 import lru_cache
+from itertools import groupby
+import jsonschema
+import six
+import uuid
+
+from sentry.db.models import BoundedIntegerField
+from sentry.constants import (
+ LOG_LEVELS_MAP,
+ MAX_TAG_KEY_LENGTH,
+ MAX_TAG_VALUE_LENGTH,
+ VALID_PLATFORMS,
+ VERSION_LENGTH,
+)
+from sentry.interfaces.base import InterfaceValidationError
+from sentry.models import EventError
+from sentry.tagstore.base import INTERNAL_TAG_KEYS
+
+
+def iverror(message="Invalid data"):
+ raise InterfaceValidationError(message)
+
+
+def apierror(message="Invalid data"):
+ from sentry.coreapi import APIForbidden
+ raise APIForbidden(message)
+
+PAIRS = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'array',
+ 'minItems': 2,
+ 'maxItems': 2,
+ 'items': {'type': 'string'}
+ }
+}
+
+HTTP_INTERFACE_SCHEMA = {
+ 'type': 'object',
+ 'properties': {
+ 'url': {
+ 'type': 'string',
+ 'minLength': 1,
+ },
+ 'method': {'type': 'string'},
+ 'query_string': {'type': ['string', 'object']},
+ 'inferred_content_type': {'type': 'string'},
+ 'cookies': {
+ 'anyOf': [
+ {'type': ['string', 'object']}, # either a string of object
+ PAIRS, # or a list of 2-tuples
+ ]
+ },
+ 'env': {'type': 'object'},
+ 'headers': {
+ 'anyOf': [
+ {'type': 'object'}, # either an object
+ PAIRS, # or a list of 2-tuples
+ ]
+ },
+ 'data': {'type': ['string', 'object', 'array']},
+ 'fragment': {'type': 'string'},
+ },
+ 'required': ['url'],
+ 'additionalProperties': True,
+}
+
+FRAME_INTERFACE_SCHEMA = {
+ 'type': 'object',
+ 'properties': {
+ 'abs_path': {
+ 'type': 'string',
+ 'default': iverror,
+ },
+ 'colno': {'type': ['number', 'string']},
+ 'context_line': {'type': 'string'},
+ 'data': {
+ 'anyOf': [
+ {'type': 'object'},
+ PAIRS,
+ ]
+ },
+ 'errors': {},
+ 'filename': {
+ 'type': 'string',
+ 'default': iverror,
+ },
+ 'function': {'type': 'string'},
+ 'image_addr': {},
+ 'in_app': {'type': 'boolean', 'default': False},
+ 'instruction_addr': {},
+ 'instruction_offset': {},
+ 'lineno': {'type': ['number', 'string']},
+ 'module': {
+ 'type': 'string',
+ 'default': iverror,
+ },
+ 'package': {'type': 'string'},
+ 'platform': {
+ 'type': 'string',
+ 'enum': list(VALID_PLATFORMS),
+ },
+ 'post_context': {},
+ 'pre_context': {},
+ 'project_root': {},
+ 'symbol': {'type': 'string'},
+ 'symbol_addr': {},
+ 'vars': {
+ 'anyOf': [
+ {'type': ['object', 'array']},
+ PAIRS,
+ ]
+ },
+ },
+ # `additionalProperties: {'not': {}}` forces additional properties to
+ # individually fail with errors that identify the key, so they can be deleted.
+ 'additionalProperties': {'not': {}},
+}
+
+STACKTRACE_INTERFACE_SCHEMA = {
+ 'type': 'object',
+ 'properties': {
+ 'frames': {
+ 'type': 'array',
+ # To validate individual frames use FRAME_INTERFACE_SCHEMA
+ 'items': {'type': 'object'},
+ 'minItems': 1,
+ },
+ 'frames_omitted': {
+ 'type': 'array',
+ 'maxItems': 2,
+ 'minItems': 2,
+ 'items': {'type': 'number'},
+ },
+ 'registers': {'type': 'object'},
+ },
+ 'required': ['frames'],
+ # `additionalProperties: {'not': {}}` forces additional properties to
+ # individually fail with errors that identify the key, so they can be deleted.
+ 'additionalProperties': {'not': {}},
+}
+
+EXCEPTION_INTERFACE_SCHEMA = {
+ 'type': 'object',
+ 'properties': {
+ 'type': {
+ 'type': 'string',
+ # 'minLength': 1,
+ },
+ 'value': {
+ # 'minProperties': 1,
+ # 'minItems': 1,
+ # 'minLength': 1,
+ },
+ 'module': {'type': 'string'},
+ 'mechanism': {'type': 'object'},
+ 'stacktrace': {
+ # To validate stacktraces use STACKTRACE_INTERFACE_SCHEMA
+ 'type': 'object',
+ 'properties': {
+ # The code allows for the possibility of an empty
+ # {"frames":[]} object, this sucks and should go.
+ # STACKTRACE_INTERFACE_SCHEMA enforces at least 1
+ 'frames': {'type': 'array'},
+ },
+ },
+ 'thread_id': {},
+ 'raw_stacktrace': {
+ 'type': 'object',
+ 'properties': {
+ 'frames': {'type': 'array'},
+ },
+ },
+ },
+ 'anyOf': [ # Require at least one of these keys.
+ {'required': ['type']},
+ {'required': ['value']},
+ ],
+ # TODO should be false but allowing extra garbage for now
+ # for compatibility
+ 'additionalProperties': True,
+}
+
+DEVICE_INTERFACE_SCHEMA = {
+ 'type': 'object',
+ 'properties': {
+ 'name': {
+ 'type': 'string',
+ 'minLength': 1,
+ },
+ 'version': {
+ 'type': 'string',
+ 'minLength': 1,
+ },
+ 'build': {},
+ 'data': {
+ 'type': 'object',
+ 'default': {},
+ },
+ },
+ 'required': ['name', 'version'],
+}
+
+TEMPLATE_INTERFACE_SCHEMA = {'type': 'object'} # TODO fill this out
+MESSAGE_INTERFACE_SCHEMA = {'type': 'object'}
+
+TAGS_DICT_SCHEMA = {
+ 'allOf': [
+ {
+ 'type': 'object',
+ # TODO with draft 6 support, we can just use propertyNames/maxLength
+ 'patternProperties': {
+ '^[a-zA-Z0-9_\.:-]{1,%d}$' % MAX_TAG_KEY_LENGTH: {
+ 'type': 'string',
+ 'minLength': 1,
+ 'maxLength': MAX_TAG_VALUE_LENGTH,
+ 'pattern': '^[^\n]+\Z', # \Z because $ matches before trailing newline
+ }
+ },
+ 'additionalProperties': False,
+ },
+ {
+ # This is a negative match for all the reserved tags
+ 'type': 'object',
+ 'patternProperties': {
+ '^(%s)$' % '|'.join(INTERNAL_TAG_KEYS): {'not': {}}
+ },
+ 'additionalProperties': True,
+ },
+ ],
+}
+
+TAGS_TUPLES_SCHEMA = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'array',
+ 'minItems': 2,
+ 'maxItems': 2,
+ 'items': [
+ # Key
+ {
+ 'type': 'string',
+ 'pattern': '^[a-zA-Z0-9_\.:-]+$',
+ 'minLength': 1,
+ 'maxLength': MAX_TAG_KEY_LENGTH,
+ 'not': {
+ 'pattern': '^(%s)$' % '|'.join(INTERNAL_TAG_KEYS),
+ },
+ },
+ # Value
+ {
+ 'type': 'string',
+ 'pattern': '^[^\n]*\Z', # \Z because $ matches before a trailing newline
+ 'minLength': 1,
+ 'maxLength': MAX_TAG_VALUE_LENGTH,
+ },
+ ]
+ }
+}
+
+TAGS_SCHEMA = {
+ 'anyOf': [TAGS_DICT_SCHEMA, TAGS_TUPLES_SCHEMA]
+}
+
+EVENT_SCHEMA = {
+ 'type': 'object',
+ 'properties': {
+ 'type': {
+ 'type': 'string',
+ },
+ 'event_id': {
+ 'type': 'string',
+ 'pattern': '^[a-fA-F0-9]+$',
+ 'maxLength': 32,
+ 'minLength': 32,
+ 'default': lambda: uuid.uuid4().hex,
+ },
+ 'timestamp': {
+ 'anyOf': [
+ {'type': 'string', 'format': 'date-time'},
+ {'type': 'number'}
+ ],
+ },
+ 'logger': {
+ 'type': 'string',
+ 'pattern': r'^[^\r\n]*\Z', # \Z because $ matches before a trailing newline
+ 'default': '',
+ },
+ 'platform': {
+ 'type': 'string',
+ 'enum': list(VALID_PLATFORMS),
+ 'default': 'other',
+ },
+ 'sdk': {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'version': {},
+ 'integrations': {},
+ },
+ 'additionalProperties': True,
+ },
+ 'level': {
+ 'anyOf': [
+ {'type': 'number'},
+ {
+ 'type': 'string',
+ 'pattern': '^[0-9]+$',
+ },
+ {
+ 'type': 'string',
+ 'enum': LOG_LEVELS_MAP.keys(),
+ },
+ ],
+ },
+ 'culprit': {
+ 'type': 'string',
+ # 'minLength': 1,
+ # 'maxLength': MAX_CULPRIT_LENGTH,
+ 'default': lambda: apierror('Invalid value for culprit'),
+ },
+ 'server_name': {'type': 'string'},
+ 'release': {
+ 'type': 'string',
+ 'maxLength': VERSION_LENGTH,
+ },
+ 'dist': {
+ 'type': 'string',
+ 'pattern': '^[a-zA-Z0-9_.-]+$',
+ 'maxLength': 64,
+ },
+ 'tags': {
+ # This is a loose tags schema, individual tags
+ # are also validated more in depth with TAGS_SCHEMA
+ 'anyOf': [
+ {'type': 'object'},
+ PAIRS,
+ ]
+ },
+ 'environment': {
+ 'type': 'string',
+ 'maxLength': 64,
+ },
+ 'modules': {'type': 'object'},
+ 'extra': {'type': 'object'},
+ 'fingerprint': {
+ 'type': 'array',
+ 'items': {'type': 'string'},
+ },
+ 'time_spent': {
+ 'type': 'number',
+ 'maximum': BoundedIntegerField.MAX_VALUE,
+ 'minimum': 0,
+ },
+
+ # Exceptions:
+ 'exception': {}, # EXCEPTION_INTERFACE_SCHEMA,
+ 'sentry.interfaces.Exception': {}, # EXCEPTION_INTERFACE_SCHEMA,
+
+ # Messages:
+ # 'message' is not an alias for the sentry.interfaces.Message interface
+ # but instead is a raw string that will be wrapped in a Message interface
+ 'message': {'type': 'string'},
+ 'logentry': {}, # MESSAGE_INTERFACE_SCHEMA,
+ 'sentry.interfaces.Message': {}, # MESSAGE_INTERFACE_SCHEMA,
+
+ # Templates:
+ 'template': {}, # TEMPLATE_INTERFACE_SCHEMA,
+ 'sentry.interfaces.Template': {}, # TEMPLATE_INTERFACE_SCHEMA,
+
+ # Other interfaces
+ 'sentry.interfaces.User': {'type': 'object'},
+ 'sentry.interfaces.Http': {},
+
+ # Other reserved keys. (some are added in processing)
+ 'project': {'type': ['number', 'string']},
+ 'key_id': {},
+ 'errors': {'type': 'array'},
+ 'checksum': {},
+ 'site': {},
+ 'received': {},
+ },
+ 'required': ['platform', 'event_id'],
+ 'additionalProperties': True,
+}
+
+CSP_SCHEMA = {
+ 'type': 'object',
+ 'properties': {
+ 'csp-report': {
+ 'type': 'object',
+ 'properties': {
+ 'effective-directive': {
+ 'type': 'string',
+ 'enum': [
+ 'base-uri',
+ 'child-src',
+ 'connect-src',
+ 'default-src',
+ 'font-src',
+ 'form-action',
+ 'frame-ancestors',
+ 'img-src',
+ 'manifest-src',
+ 'media-src',
+ 'object-src',
+ 'plugin-types',
+ 'referrer',
+ 'script-src',
+ 'style-src',
+ 'upgrade-insecure-requests',
+ # 'frame-src', # Deprecated (https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives#frame-src)
+ # 'sandbox', # Unsupported
+ ],
+ },
+ 'blocked-uri': {
+ 'type': 'string',
+ 'default': 'self',
+ },
+ 'document-uri': {
+ 'type': 'string',
+ 'not': {'enum': ['about:blank']}
+ },
+ 'original-policy': {'type': 'string'},
+ 'referrer': {'type': 'string', 'default': ''},
+ 'status-code': {'type': 'number'},
+ 'violated-directive': {'type': 'string', 'default': ''},
+ 'source-file': {'type': 'string'},
+ 'line-number': {'type': 'number'},
+ 'column-number': {'type': 'number'},
+ 'script-sample': {},
+ 'disposition': {'type': 'string'},
+ },
+ 'required': ['effective-directive'],
+ # Allow additional keys as browser vendors are still changing CSP
+ # implementations fairly frequently
+ 'additionalProperties': True,
+ }
+ },
+ 'required': ['csp-report'],
+ 'additionalProperties': False,
+}
+
+CSP_INTERFACE_SCHEMA = {
+ 'type': 'object',
+ 'properties': {k.replace('-', '_'): v for k, v in six.iteritems(CSP_SCHEMA['properties']['csp-report']['properties'])},
+ 'required': ['effective_directive', 'violated_directive', 'blocked_uri'],
+ 'additionalProperties': False, # Don't allow any other keys.
+}
+
+EXPECT_CT_SCHEMA = {
+ 'type': 'object',
+ 'properties': {
+ 'expect-ct-report': {
+ 'type': 'object',
+ 'properties': {
+ 'date-time': {
+ 'type': 'string',
+ 'format': 'date-time',
+ },
+ 'hostname': {'type': 'string'},
+ 'port': {'type': 'number'},
+ 'effective-expiration-date': {
+ 'type': 'string',
+ 'format': 'date-time',
+ },
+ 'served-certificate-chain': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ 'validated-certificate-chain': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ 'scts': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'version': {'type': 'number'},
+ 'status': {
+ 'type': 'string',
+ 'enum': ['unknown', 'valid', 'invalid'],
+ },
+ 'source': {
+ 'type': 'string',
+ 'enum': ['tls-extension', 'ocsp', 'embedded'],
+ },
+ 'serialized_sct': {'type': 'string'}, # Base64
+ },
+ 'additionalProperties': False,
+ },
+ },
+ },
+ 'required': ['hostname'],
+ 'additionalProperties': False,
+ },
+ },
+ 'additionalProperties': False,
+}
+
+EXPECT_CT_INTERFACE_SCHEMA = {
+ 'type': 'object',
+ 'properties': {k.replace('-', '_'): v for k, v in
+ six.iteritems(EXPECT_CT_SCHEMA['properties']['expect-ct-report']['properties'])},
+ 'required': ['hostname'],
+ 'additionalProperties': False,
+}
+
+EXPECT_STAPLE_SCHEMA = {
+ 'type': 'object',
+ 'properties': {
+ 'expect-staple-report': {
+ 'type': 'object',
+ 'properties': {
+ 'date-time': {
+ 'type': 'string',
+ 'format': 'date-time',
+ },
+ 'hostname': {'type': 'string'},
+ 'port': {'type': 'number'},
+ 'effective-expiration-date': {
+ 'type': 'string',
+ 'format': 'date-time',
+ },
+ 'response-status': {
+ 'type': 'string',
+ 'enum': [
+ 'MISSING',
+ 'PROVIDED',
+ 'ERROR_RESPONSE',
+ 'BAD_PRODUCED_AT',
+ 'NO_MATCHING_RESPONSE',
+ 'INVALID_DATE',
+ 'PARSE_RESPONSE_ERROR',
+ 'PARSE_RESPONSE_DATA_ERROR',
+ ],
+ },
+ 'ocsp-response': {},
+ 'cert-status': {
+ 'type': 'string',
+ 'enum': [
+ 'GOOD',
+ 'REVOKED',
+ 'UNKNOWN',
+ ],
+ },
+ 'served-certificate-chain': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ 'validated-certificate-chain': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ },
+ 'required': ['hostname'],
+ 'additionalProperties': False,
+ },
+ },
+ 'additionalProperties': False,
+}
+
+EXPECT_STAPLE_INTERFACE_SCHEMA = {
+ 'type': 'object',
+ 'properties': {k.replace('-', '_'): v for k, v in
+ six.iteritems(EXPECT_STAPLE_SCHEMA['properties']['expect-staple-report']['properties'])},
+ 'required': ['hostname'],
+ 'additionalProperties': False,
+}
+
+"""
+Schemas for raw request data.
+
+This is to validate input data at the very first stage of ingestion. It can
+then be transformed into the requisite interface.
+"""
+INPUT_SCHEMAS = {
+ 'sentry.interfaces.Csp': CSP_SCHEMA,
+ 'expectct': EXPECT_CT_SCHEMA,
+ 'expectstaple': EXPECT_STAPLE_SCHEMA,
+}
+
+"""
+Schemas for interfaces.
+
+Data returned by interface.to_json() or passed into interface.to_python()
+should conform to these schemas. Currently this is not enforced everywhere yet.
+"""
+INTERFACE_SCHEMAS = {
+ # Sentry interfaces
+ 'sentry.interfaces.Http': HTTP_INTERFACE_SCHEMA,
+ 'request': HTTP_INTERFACE_SCHEMA,
+ 'exception': EXCEPTION_INTERFACE_SCHEMA,
+ 'sentry.interfaces.Exception': EXCEPTION_INTERFACE_SCHEMA,
+ 'stacktrace': STACKTRACE_INTERFACE_SCHEMA,
+ 'sentry.interfaces.Stacktrace': STACKTRACE_INTERFACE_SCHEMA,
+ 'frame': FRAME_INTERFACE_SCHEMA, # Not listed in SENTRY_INTERFACES
+ 'logentry': MESSAGE_INTERFACE_SCHEMA,
+ 'sentry.interfaces.Message': MESSAGE_INTERFACE_SCHEMA,
+ 'template': TEMPLATE_INTERFACE_SCHEMA,
+ 'sentry.interfaces.Template': TEMPLATE_INTERFACE_SCHEMA,
+ 'device': DEVICE_INTERFACE_SCHEMA,
+
+ # Security reports
+ 'sentry.interfaces.Csp': CSP_INTERFACE_SCHEMA,
+ 'expectct': EXPECT_CT_INTERFACE_SCHEMA,
+ 'expectstaple': EXPECT_STAPLE_INTERFACE_SCHEMA,
+
+ # Not interfaces per se, but looked up as if they were.
+ 'event': EVENT_SCHEMA,
+ 'tags': TAGS_TUPLES_SCHEMA,
+}
+
+
+@lru_cache(maxsize=100)
+def validator_for_interface(name):
+ if name not in INTERFACE_SCHEMAS:
+ return None
+ return jsonschema.Draft4Validator(
+ INTERFACE_SCHEMAS[name],
+ types={'array': (list, tuple)},
+ format_checker=jsonschema.FormatChecker()
+ )
+
+
+def validate_and_default_interface(data, interface, name=None,
+ strip_nones=True, raise_on_invalid=False):
+ """
+ Modify data to conform to named interface's schema.
+
+ Takes the object in `data` and checks it against the schema for
+ `interface`, removing or defaulting any keys that do not pass validation
+ and adding defaults for any keys that are required by (and have a default
+ value in) the schema.
+
+ Returns whether the resulting modified data is valid against the schema and
+ a list of any validation errors encountered in processing.
+ """
+ is_valid = True
+ needs_revalidation = False
+ errors = []
+
+ validator = validator_for_interface(interface)
+ if validator is None:
+ return (True, [])
+ schema = validator.schema
+
+ # Strip Nones so we don't have to take null into account for all schemas.
+ if strip_nones and isinstance(data, dict):
+ for k in data.keys():
+ if data[k] is None:
+ del data[k]
+
+ # Values that are missing entirely, but are required and should be defaulted
+ if 'properties' in schema and 'required' in schema and isinstance(data, dict):
+ for p in schema['required']:
+ if p not in data:
+ if p in schema['properties'] and 'default' in schema['properties'][p]:
+ default = schema['properties'][p]['default']
+ data[p] = default() if callable(default) else default
+ else:
+ # TODO raise as shortcut?
+ errors.append({'type': EventError.MISSING_ATTRIBUTE, 'name': p})
+
+ validator_errors = list(validator.iter_errors(data))
+ keyed_errors = [e for e in reversed(validator_errors) if len(e.path)]
+ if len(validator_errors) > len(keyed_errors):
+ needs_revalidation = True
+
+ # Values that need to be defaulted or deleted because they are not valid.
+ for key, group in groupby(keyed_errors, lambda e: e.path[0]):
+ ve = six.next(group)
+ is_max = ve.validator.startswith('max')
+ error_type = EventError.VALUE_TOO_LONG if is_max else EventError.INVALID_DATA
+ errors.append({'type': error_type, 'name': name or key, 'value': data[key]})
+
+ if 'default' in ve.schema:
+ default = ve.schema['default']
+ data[key] = default() if callable(default) else default
+ else:
+ needs_revalidation = True
+ del data[key]
+
+ if needs_revalidation:
+ is_valid = validator.is_valid(data)
+
+ return is_valid, errors
diff --git a/src/sentry/interfaces/security.py b/src/sentry/interfaces/security.py
new file mode 100644
index 00000000000000..263cc398174284
--- /dev/null
+++ b/src/sentry/interfaces/security.py
@@ -0,0 +1,467 @@
+"""
+sentry.interfaces.security
+~~~~~~~~~~~~~~~~~~~~~
+
+:copyright: (c) 2010-2015 by the Sentry Team, see AUTHORS for more details.
+:license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import absolute_import
+
+import jsonschema
+import six
+
+__all__ = ('Csp', 'ExpectCT', 'ExpectStaple')
+
+from six.moves.urllib.parse import urlsplit, urlunsplit
+
+from sentry.interfaces.base import Interface, InterfaceValidationError
+from sentry.interfaces.schemas import validate_and_default_interface, INPUT_SCHEMAS
+from sentry.utils import json
+from sentry.utils.cache import memoize
+from sentry.utils.http import is_valid_origin
+from sentry.utils.safe import trim
+from sentry.web.helpers import render_to_string
+
+# Default block list sourced from personal experience as well as
+# reputable blogs from Twitter and Dropbox
+DEFAULT_DISALLOWED_SOURCES = (
+ 'about', # Noise from Chrome about page.
+ 'ms-browser-extension',
+ 'chrome://*',
+ 'chrome-extension://*',
+ 'chromeinvokeimmediate://*'
+ 'chromenull://*',
+ 'safari-extension://*',
+ 'mxaddon-pkg://*',
+ 'jar://*',
+ 'webviewprogressproxy://*',
+ 'ms-browser-extension://*',
+ 'tmtbff://*',
+ 'mbinit://*',
+ 'symres://*',
+ 'resource://*',
+ '*.metrext.com',
+ 'static.image2play.com',
+ '*.tlscdn.com',
+ '73a5b0806e464be8bd4e694c744624f0.com',
+ '020dfefc4ac745dab7594f2f771c1ded.com',
+ '*.superfish.com',
+ 'addons.mozilla.org',
+ 'v.zilionfast.in',
+ 'widgets.amung.us',
+ '*.superfish.com',
+ 'xls.searchfun.in',
+ 'istatic.datafastguru.info',
+ 'v.zilionfast.in',
+ 'localhost',
+ 'resultshub-a.akamaihd.net',
+ 'pulseadnetwork.com',
+ 'gateway.zscalertwo.net',
+ 'www.passpack.com',
+ 'middlerush-a.akamaihd.net',
+ 'www.websmartcenter.com',
+ 'a.linkluster.com',
+ 'saveyoutime.ru',
+ 'cdncache-a.akamaihd.net',
+ 'x.rafomedia.com',
+ 'savingsslider-a.akamaihd.net',
+ 'injections.adguard.com',
+ 'icontent.us',
+ 'amiok.org',
+ 'connectionstrenth.com',
+ 'siteheart.net',
+ 'netanalitics.space',
+) # yapf: disable
+
+
+class SecurityReport(Interface):
+ """
+ A browser security violation report.
+ """
+
+ path = None
+ title = None
+
+ @classmethod
+ def from_raw(cls, raw):
+ """
+ Constructs the interface from a raw security report request body
+
+ This is usually slightly different than to_python as it needs to
+ do some extra validation, data extraction / default setting.
+ """
+ raise NotImplementedError
+
+ @classmethod
+ def to_python(cls, data):
+ is_valid, errors = validate_and_default_interface(data, cls.path)
+ if not is_valid:
+ raise InterfaceValidationError("Invalid interface data")
+
+ return cls(**data)
+
+ def get_culprit(self):
+ raise NotImplementedError
+
+ def get_message(self):
+ raise NotImplementedError
+
+ def get_path(self):
+ return self.path
+
+ def get_tags(self):
+ raise NotImplementedError
+
+ def get_title(self):
+ return self.title
+
+ def should_filter(self, project=None):
+ raise NotImplementedError
+
+ def get_origin(self):
+ """
+ The document URL that generated this report
+ """
+ raise NotImplementedError
+
+ def get_referrer(self):
+ """
+ The referrer of the page that generated this report.
+ """
+ raise NotImplementedError
+
+
+class ExpectStaple(SecurityReport):
+ """
+ An OCSP Stapling violation report
+
+ See: https://docs.google.com/document/d/1aISglJIIwglcOAhqNfK-2vtQl-_dWAapc-VLDh-9-BE
+ >>> {
+ >>> "date-time": date-time,
+ >>> "hostname": hostname,
+ >>> "port": port,
+ >>> "effective-expiration-date": date-time,
+ >>> "response-status": ResponseStatus,
+ >>> "ocsp-response": ocsp,
+ >>> "cert-status": CertStatus,
+ >>> "served-certificate-chain": [pem1, ... pemN],(MUST be in the order served)
+ >>> "validated-certificate-chain": [pem1, ... pemN](MUST be in the order served)
+ >>> }
+ """
+
+ score = 1300
+ display_score = 1300
+
+ path = 'expectstaple'
+ title = 'Expect-Staple Report'
+
+ @classmethod
+ def from_raw(cls, raw):
+ # Validate the raw data against the input schema (raises on failure)
+ schema = INPUT_SCHEMAS[cls.path]
+ jsonschema.validate(raw, schema)
+
+ # For Expect-Staple, the values we want are nested under the
+ # 'expect-staple-report' key.
+ raw = raw['expect-staple-report']
+ # Trim values and convert keys to use underscores
+ kwargs = {k.replace('-', '_'): trim(v, 1024) for k, v in six.iteritems(raw)}
+
+ return cls.to_python(kwargs)
+
+ def get_culprit(self):
+ return self.hostname
+
+ def get_hash(self, is_processed_data=True):
+ return [self.hostname]
+
+ def get_message(self):
+ return "Expect-Staple failed for '{self.hostname}'".format(self=self)
+
+ def get_tags(self):
+ return (
+ ('port', six.text_type(self.port)),
+ ('hostname', self.hostname),
+ ('response_status', self.response_status),
+ ('cert_status', self.cert_status),
+ )
+
+ def get_origin(self):
+ return self.hostname
+
+ def get_referrer(self):
+ return None
+
+ def should_filter(self, project=None):
+ return False
+
+
+class ExpectCT(SecurityReport):
+ """
+ A Certificate Transparency violation report.
+
+ See also: http://httpwg.org/http-extensions/expect-ct.html
+ >>> {
+ >>> "date-time": "2014-04-06T13:00:50Z",
+ >>> "hostname": "www.example.com",
+ >>> "port": 443,
+ >>> "effective-expiration-date": "2014-05-01T12:40:50Z",
+ >>> "served-certificate-chain": [],
+ >>> "validated-certificate-chain": [],
+ >>> "scts-pins": [],
+ >>> }
+ """
+
+ score = 1300
+ display_score = 1300
+
+ path = 'expectct'
+ title = 'Expect-CT Report'
+
+ @classmethod
+ def from_raw(cls, raw):
+ # Validate the raw data against the input schema (raises on failure)
+ schema = INPUT_SCHEMAS[cls.path]
+ jsonschema.validate(raw, schema)
+
+ # For Expect-CT, the values we want are nested under the 'expect-ct-report' key.
+ raw = raw['expect-ct-report']
+ # Trim values and convert keys to use underscores
+ kwargs = {k.replace('-', '_'): trim(v, 1024) for k, v in six.iteritems(raw)}
+
+ return cls.to_python(kwargs)
+
+ def get_culprit(self):
+ return self.hostname
+
+ def get_hash(self, is_processed_data=True):
+ return [self.hostname]
+
+ def get_message(self):
+ return "Expect-CT failed for '{self.hostname}'".format(self=self)
+
+ def get_tags(self):
+ return (
+ ('port', six.text_type(self.port)),
+ ('hostname', self.hostname),
+ )
+
+ def get_origin(self):
+ return self.hostname # not quite origin, but the domain that failed pinning
+
+ def get_referrer(self):
+ return None
+
+ def should_filter(self, project=None):
+ return False
+
+
+class Csp(SecurityReport):
+ """
+ A CSP violation report.
+
+ See also: http://www.w3.org/TR/CSP/#violation-reports
+
+ >>> {
+ >>> "document_uri": "http://example.com/",
+ >>> "violated_directive": "style-src cdn.example.com",
+ >>> "blocked_uri": "http://example.com/style.css",
+ >>> "effective_directive": "style-src",
+ >>> }
+ """
+
+ LOCAL = "'self'"
+ score = 1300
+ display_score = 1300
+
+ path = 'sentry.interfaces.Csp'
+ title = 'CSP Report'
+
+ @classmethod
+ def from_raw(cls, raw):
+ # Validate the raw data against the input schema (raises on failure)
+ schema = INPUT_SCHEMAS[cls.path]
+ jsonschema.validate(raw, schema)
+
+ # For CSP, the values we want are nested under the 'csp-report' key.
+ raw = raw['csp-report']
+ # Trim values and convert keys to use underscores
+ kwargs = {k.replace('-', '_'): trim(v, 1024) for k, v in six.iteritems(raw)}
+
+ return cls.to_python(kwargs)
+
+ def get_hash(self, is_processed_data=True):
+ if self._local_script_violation_type:
+ uri = "'%s'" % self._local_script_violation_type
+ else:
+ uri = self._normalized_blocked_uri
+
+ return [self.effective_directive, uri]
+
+ def get_message(self):
+ templates = {
+ 'child-src': (u"Blocked 'child' from '{uri}'", "Blocked inline 'child'"),
+ 'connect-src': (u"Blocked 'connect' from '{uri}'", "Blocked inline 'connect'"),
+ 'font-src': (u"Blocked 'font' from '{uri}'", "Blocked inline 'font'"),
+ 'form-action': (u"Blocked 'form' action to '{uri}'", ), # no inline option
+ 'img-src': (u"Blocked 'image' from '{uri}'", "Blocked inline 'image'"),
+ 'manifest-src': (u"Blocked 'manifest' from '{uri}'", "Blocked inline 'manifest'"),
+ 'media-src': (u"Blocked 'media' from '{uri}'", "Blocked inline 'media'"),
+ 'object-src': (u"Blocked 'object' from '{uri}'", "Blocked inline 'object'"),
+ 'script-src': (u"Blocked 'script' from '{uri}'", "Blocked unsafe (eval() or inline) 'script'"),
+ 'style-src': (u"Blocked 'style' from '{uri}'", "Blocked inline 'style'"),
+ 'unsafe-inline': (None, u"Blocked unsafe inline 'script'"),
+ 'unsafe-eval': (None, u"Blocked unsafe eval() 'script'"),
+ }
+ default_template = ('Blocked {directive!r} from {uri!r}', 'Blocked inline {directive!r}')
+
+ directive = self._local_script_violation_type or self.effective_directive
+ uri = self._normalized_blocked_uri
+ index = 1 if uri == self.LOCAL else 0
+
+ try:
+ tmpl = templates[directive][index]
+ except (KeyError, IndexError):
+ tmpl = default_template[index]
+
+ return tmpl.format(directive=directive, uri=uri)
+
+ def get_culprit(self):
+ if not self.violated_directive:
+ return ''
+ bits = [d for d in self.violated_directive.split(' ') if d]
+ return ' '.join([bits[0]] + [self._normalize_value(b) for b in bits[1:]])
+
+ def get_tags(self):
+ return [
+ ('effective-directive', self.effective_directive),
+ ('blocked-uri', self._sanitized_blocked_uri()),
+ ]
+
+ def get_origin(self):
+ return self.document_uri
+
+ def get_referrer(self):
+ return self.referrer
+
+ def to_string(self, is_public=False, **kwargs):
+ return json.dumps({'csp-report': self.get_api_context()}, indent=2)
+
+ def to_email_html(self, event, **kwargs):
+ return render_to_string(
+ 'sentry/partial/interfaces/csp_email.html', {'data': self.get_api_context()}
+ )
+
+ def should_filter(self, project=None):
+ disallowed = ()
+ paths = ['blocked_uri', 'source_file']
+ uris = [getattr(self, path) for path in paths if hasattr(self, path)]
+
+ if project is None or bool(project.get_option('sentry:csp_ignored_sources_defaults', True)):
+ disallowed += DEFAULT_DISALLOWED_SOURCES
+ if project is not None:
+ disallowed += tuple(project.get_option('sentry:csp_ignored_sources', []))
+
+ if disallowed and any(is_valid_origin(uri and uri, allowed=disallowed) for uri in uris):
+ return True
+
+ return False
+
+ def _sanitized_blocked_uri(self):
+ # HACK: This is 100% to work around Stripe urls
+ # that will casually put extremely sensitive information
+ # in querystrings. The real solution is to apply
+ # data scrubbing to all tags generically
+ # TODO this could be done in filter_csp
+ # instead but that might only be run conditionally on the org/project settings
+ # relevant code is @L191:
+ #
+ # if netloc == 'api.stripe.com':
+ # query = ''
+ # fragment = ''
+
+ uri = self.blocked_uri
+ if uri.startswith('https://api.stripe.com/'):
+ return urlunsplit(urlsplit(uri)[:3] + (None, None))
+ return uri
+
+ @memoize
+ def _normalized_blocked_uri(self):
+ return self._normalize_uri(self.blocked_uri)
+
+ @memoize
+ def _normalized_document_uri(self):
+ return self._normalize_uri(self.document_uri)
+
+ def _normalize_value(self, value):
+ keywords = ("'none'", "'self'", "'unsafe-inline'", "'unsafe-eval'", )
+ all_schemes = (
+ 'data:',
+ 'mediastream:',
+ 'blob:',
+ 'filesystem:',
+ 'http:',
+ 'https:',
+ 'file:',
+ )
+
+ # > If no scheme is specified, the same scheme as the one used to
+ # > access the protected document is assumed.
+ # Source: https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives
+ if value in keywords:
+ return value
+
+ # normalize a value down to 'self' if it matches the origin of document-uri
+ # FireFox transforms a 'self' value into the spelled out origin, so we
+ # want to reverse this and bring it back
+ if value.startswith(all_schemes):
+ if self._normalized_document_uri == self._normalize_uri(value):
+ return self.LOCAL
+ # Their rule had an explicit scheme, so let's respect that
+ return value
+
+ # value doesn't have a scheme, but let's see if their
+ # hostnames match at least, if so, they're the same
+ if value == self._normalized_document_uri:
+ return self.LOCAL
+
+ # Now we need to stitch on a scheme to the value
+ scheme = self.document_uri.split(':', 1)[0]
+ # But let's not stitch on the boring values
+ if scheme in ('http', 'https'):
+ return value
+ return self._unsplit(scheme, value)
+
+ @memoize
+ def _local_script_violation_type(self):
+ """
+ If this is a locally-sourced script-src error, gives the type.
+ """
+ if (self.violated_directive
+ and self.effective_directive == 'script-src'
+ and self._normalized_blocked_uri == self.LOCAL):
+ if "'unsafe-inline'" in self.violated_directive:
+ return "unsafe-inline"
+ elif "'unsafe-eval'" in self.violated_directive:
+ return "unsafe-eval"
+ return None
+
+ def _normalize_uri(self, value):
+ if value in ('', self.LOCAL, self.LOCAL.strip("'")):
+ return self.LOCAL
+
+ # A lot of these values get reported as literally
+ # just the scheme. So a value like 'data' or 'blob', which
+ # are valid schemes, just not a uri. So we want to
+ # normalize it into a uri.
+ if ':' not in value:
+ scheme, hostname = value, ''
+ else:
+ scheme, hostname = urlsplit(value)[:2]
+ if scheme in ('http', 'https'):
+ return hostname
+ return self._unsplit(scheme, hostname)
+
+ def _unsplit(self, scheme, hostname):
+ return urlunsplit((scheme, hostname, '', None, None))
diff --git a/src/sentry/interfaces/stacktrace.py b/src/sentry/interfaces/stacktrace.py
index 66168c13fc26e2..ff54bd1a1e4f6c 100644
--- a/src/sentry/interfaces/stacktrace.py
+++ b/src/sentry/interfaces/stacktrace.py
@@ -20,10 +20,10 @@
from sentry.app import env
from sentry.interfaces.base import Interface, InterfaceValidationError
+from sentry.interfaces.schemas import validate_and_default_interface
from sentry.models import UserOption
from sentry.utils.safe import trim, trim_dict
from sentry.web.helpers import render_to_string
-from sentry.constants import VALID_PLATFORMS
_ruby_anon_func = re.compile(r'_\d{2,}')
_filename_version_re = re.compile(
@@ -141,7 +141,7 @@ def is_newest_frame_first(event):
def is_url(filename):
- return filename.startswith(('file:', 'http:', 'https:'))
+ return filename.startswith(('file:', 'http:', 'https:', 'applewebdata:'))
def remove_function_outliers(function):
@@ -253,8 +253,15 @@ def handle_nan(value):
class Frame(Interface):
+
+ path = 'frame'
+
@classmethod
def to_python(cls, data, raw=False):
+ is_valid, errors = validate_and_default_interface(data, cls.path)
+ if not is_valid:
+ raise InterfaceValidationError("Invalid stack frame data.")
+
abs_path = data.get('abs_path')
filename = data.get('filename')
symbol = data.get('symbol')
@@ -270,11 +277,6 @@ def to_python(cls, data, raw=False):
if symbol == '?':
symbol = None
- for name in ('abs_path', 'filename', 'symbol', 'function', 'module', 'package'):
- v = data.get(name)
- if v is not None and not isinstance(v, six.string_types):
- raise InterfaceValidationError("Invalid value for '%s'" % name)
-
# Some of this processing should only be done for non raw frames
if not raw:
# absolute path takes priority over filename
@@ -293,15 +295,12 @@ def to_python(cls, data, raw=False):
else:
filename = abs_path
- if not (filename or function or module or package):
- raise InterfaceValidationError(
- "No 'filename' or 'function' or "
- "'module' or 'package'"
- )
+ if not (filename or function or module or package):
+ raise InterfaceValidationError(
+ "No 'filename' or 'function' or 'module' or 'package'"
+ )
platform = data.get('platform')
- if platform not in VALID_PLATFORMS:
- platform = None
context_locals = data.get('vars') or {}
if isinstance(context_locals, (list, tuple)):
@@ -329,10 +328,7 @@ def to_python(cls, data, raw=False):
else:
pre_context, post_context = None, None
- try:
- in_app = validate_bool(data.get('in_app'), False)
- except AssertionError:
- raise InterfaceValidationError("Invalid value for 'in_app'")
+ in_app = validate_bool(data.get('in_app'), False)
kwargs = {
'abs_path': trim(abs_path, 2048),
@@ -541,7 +537,7 @@ def get_culprit_string(self, platform=None):
fileloc = self.module or self.filename
if not fileloc:
return ''
- elif platform == 'javascript':
+ elif platform in ('javascript', 'node'):
# function and fileloc might be unicode here, so let it coerce
# to a unicode string if needed.
return '%s(%s)' % (self.function or '?', fileloc)
@@ -648,17 +644,16 @@ class Stacktrace(Interface):
to the full interface path.
"""
score = 2000
+ path = 'sentry.interfaces.Stacktrace'
def __iter__(self):
return iter(self.frames)
@classmethod
def to_python(cls, data, slim_frames=True, raw=False):
- if not data.get('frames'):
- raise InterfaceValidationError("No 'frames' present")
-
- if not isinstance(data['frames'], list):
- raise InterfaceValidationError("Invalid value for 'frames'")
+ is_valid, errors = validate_and_default_interface(data, cls.path)
+ if not is_valid:
+ raise InterfaceValidationError("Invalid stack frame data.")
frame_list = [
# XXX(dcramer): handle PHP sending an empty array for a frame
@@ -674,8 +669,6 @@ def to_python(cls, data, slim_frames=True, raw=False):
kwargs['registers'] = data.get('registers')
if data.get('frames_omitted'):
- if len(data['frames_omitted']) != 2:
- raise InterfaceValidationError("Invalid value for 'frames_omitted'")
kwargs['frames_omitted'] = data['frames_omitted']
else:
kwargs['frames_omitted'] = None
@@ -721,7 +714,7 @@ def to_json(self):
}
def get_path(self):
- return 'sentry.interfaces.Stacktrace'
+ return self.path
def compute_hashes(self, platform):
system_hash = self.get_hash(platform, system_frames=True)
diff --git a/src/sentry/interfaces/user.py b/src/sentry/interfaces/user.py
index 9407847bee5dcf..c69ae5d65cb873 100644
--- a/src/sentry/interfaces/user.py
+++ b/src/sentry/interfaces/user.py
@@ -57,9 +57,9 @@ def to_python(cls, data):
if not isinstance(extra_data, dict):
extra_data = {}
- ident = trim(data.pop('id', None), 128)
+ ident = data.pop('id', None)
if ident:
- ident = six.text_type(ident)
+ ident = trim(six.text_type(ident), 128)
try:
email = trim(validate_email(data.pop('email', None), False), MAX_EMAIL_FIELD_LENGTH)
except ValueError:
diff --git a/src/sentry/lang/java/plugin.py b/src/sentry/lang/java/plugin.py
index 862d6f9764654c..5b46c6aa8da908 100644
--- a/src/sentry/lang/java/plugin.py
+++ b/src/sentry/lang/java/plugin.py
@@ -1,6 +1,9 @@
from __future__ import absolute_import
-from libsourcemap import ProguardView
+import six
+import uuid
+
+from symbolic import ProguardMappingView
from sentry.plugins import Plugin2
from sentry.stacktraces import StacktraceProcessor
from sentry.models import ProjectDSymFile, EventError
@@ -19,7 +22,7 @@ def __init__(self, *args, **kwargs):
self.debug_meta = debug_meta
for img in debug_meta['images']:
if img['type'] == 'proguard':
- self.images.add(img['uuid'])
+ self.images.add(uuid.UUID(img['uuid']))
else:
self.available = False
@@ -32,7 +35,7 @@ def preprocess_frame(self, processable_frame):
(
FRAME_CACHE_VERSION, processable_frame.frame['module'],
processable_frame.frame['function'],
- ) + tuple(sorted(self.images))
+ ) + tuple(sorted(map(six.text_type, self.images)))
)
def preprocess_step(self, processing_task):
@@ -49,7 +52,7 @@ def preprocess_step(self, processing_task):
if dsym_path is None:
error_type = EventError.PROGUARD_MISSING_MAPPING
else:
- view = ProguardView.from_path(dsym_path)
+ view = ProguardMappingView.from_path(dsym_path)
if not view.has_line_info:
error_type = EventError.PROGUARD_MISSING_LINENO
else:
@@ -61,7 +64,7 @@ def preprocess_step(self, processing_task):
self.data.setdefault('errors',
[]).append({
'type': error_type,
- 'mapping_uuid': image_uuid,
+ 'mapping_uuid': six.text_type(image_uuid),
})
report_processing_issue(
self.data,
@@ -69,10 +72,12 @@ def preprocess_step(self, processing_task):
object='mapping:%s' % image_uuid,
type=error_type,
data={
- 'mapping_uuid': image_uuid,
+ 'mapping_uuid': six.text_type(image_uuid),
}
)
+ return True
+
def process_frame(self, processable_frame, processing_task):
new_module = None
new_function = None
diff --git a/src/sentry/lang/javascript/cache.py b/src/sentry/lang/javascript/cache.py
index 9edbeec5e47baa..65f8d68311a22f 100644
--- a/src/sentry/lang/javascript/cache.py
+++ b/src/sentry/lang/javascript/cache.py
@@ -1,10 +1,17 @@
from __future__ import absolute_import, print_function
+from six import text_type
+from symbolic import SourceView
from sentry.utils.strings import codec_lookup
__all__ = ['SourceCache', 'SourceMapCache']
+def is_utf8(codec):
+ name = codec_lookup(codec).name
+ return name in ('utf-8', 'ascii')
+
+
class SourceCache(object):
def __init__(self):
self._cache = {}
@@ -20,55 +27,33 @@ def _get_canonical_url(self, url):
url = self._aliases[url]
return url
- def get(self, url, raw=False):
- url = self._get_canonical_url(url)
- try:
- parsed, rv = self._cache[url]
- except KeyError:
- return None
-
- # We have already gotten this file and we've
- # decoded the response, so just return
- if parsed:
- parsed, raw_body = rv
- if raw:
- return raw_body
- return parsed
-
- # Otherwise, we have a 2-tuple that needs to be applied
- body, encoding = rv
-
- # Our body is lazily evaluated if it
- # comes from libsourcemap
- if callable(body):
- body = body()
-
- raw_body = body
- body = body.decode(codec_lookup(encoding, 'utf-8').name, 'replace').split(u'\n')
-
- # Set back a marker to indicate we've parsed this url
- self._cache[url] = (True, (body, raw_body))
- return body
+ def get(self, url):
+ return self._cache.get(self._get_canonical_url(url))
def get_errors(self, url):
url = self._get_canonical_url(url)
return self._errors.get(url, [])
- def alias(self, u1, u2):
- if u1 == u2:
- return
-
- if u1 in self._cache or u1 not in self._aliases:
- self._aliases[u1] = u1
- else:
- self._aliases[u2] = u1
+ def alias(self, alias, target):
+ if alias != target:
+ self._aliases[alias] = target
def add(self, url, source, encoding=None):
url = self._get_canonical_url(url)
- # Insert into the cache, an unparsed (source, encoding)
- # tuple. This allows the source to be split and decoded
- # on demand when first accessed.
- self._cache[url] = (False, (source, encoding))
+
+ if not isinstance(source, SourceView):
+ if isinstance(source, text_type):
+ source = source.encode('utf-8')
+ # If an encoding is provided and it's not utf-8 compatible
+ # we try to re-encoding the source and create a source view
+ # from it.
+ elif encoding is not None and not is_utf8(encoding):
+ try:
+ source = source.decode(encoding).encode('utf-8')
+ except UnicodeError:
+ pass
+ source = SourceView.from_bytes(source)
+ self._cache[url] = source
def add_error(self, url, error):
url = self._get_canonical_url(url)
diff --git a/src/sentry/lang/javascript/errorlocale.py b/src/sentry/lang/javascript/errorlocale.py
new file mode 100644
index 00000000000000..3cd678cef8f0a7
--- /dev/null
+++ b/src/sentry/lang/javascript/errorlocale.py
@@ -0,0 +1,98 @@
+from __future__ import absolute_import, print_function
+
+import six
+import os
+import io
+import re
+
+LOCALES_DIR = os.path.join(os.path.dirname(__file__), '../../data/error-locale')
+TARGET_LOCALE = 'en-US'
+
+translation_lookup_table = set()
+target_locale_lookup_table = {}
+
+for locale in os.listdir(LOCALES_DIR):
+ fn = os.path.join(LOCALES_DIR, locale)
+ if not os.path.isfile(fn):
+ continue
+
+ with io.open(fn, encoding='utf-8') as f:
+ for line in f:
+ key, translation = line.split(',', 1)
+ translation = translation.strip()
+
+ if TARGET_LOCALE in locale:
+ target_locale_lookup_table[key] = translation
+ else:
+ translation_regexp = re.escape(translation)
+ translation_regexp = translation_regexp.replace(
+ '\%s', r'(?P[a-zA-Z0-9-_\$]+)')
+ # Some errors are substrings of more detailed ones, so we need exact match
+ translation_regexp = re.compile('^' + translation_regexp + '$')
+ translation_lookup_table.add((translation_regexp, key))
+
+
+def find_translation(message):
+ for translation in translation_lookup_table:
+ translation_regexp, key = translation
+ match = translation_regexp.search(message)
+
+ if match is not None:
+ format_string_data = match.groupdict().get('format_string_data')
+
+ if format_string_data is None:
+ return [key, None]
+ else:
+ return [key, format_string_data]
+
+ return [None, None]
+
+
+def format_message(message, data):
+ return message.replace('%s', data)
+
+message_type_regexp = re.compile('^(?P[a-zA-Z]*Error): (?P.*)')
+
+
+def translate_message(original_message):
+ if not isinstance(original_message, six.string_types):
+ return original_message
+
+ type = None
+ message = original_message.strip()
+
+ # Handle both cases. Just a message and message preceeded with error type
+ # eg. `ReferenceError: foo`, `TypeError: bar`
+ match = message_type_regexp.search(message)
+
+ if match is not None:
+ type = match.groupdict().get('type')
+ message = match.groupdict().get('message')
+
+ translation, format_string_data = find_translation(message)
+
+ if translation is None:
+ return original_message
+ else:
+ translated_message = target_locale_lookup_table.get(translation, original_message)
+
+ if type is not None:
+ translated_message = type + ': ' + translated_message
+
+ if format_string_data is None:
+ return translated_message
+ else:
+ return format_message(translated_message, format_string_data)
+
+
+def translate_exception(data):
+ if 'sentry.interfaces.Message' in data:
+ data['sentry.interfaces.Message']['message'] = translate_message(
+ data['sentry.interfaces.Message']['message'])
+
+ if 'sentry.interfaces.Exception' in data:
+ for entry in data['sentry.interfaces.Exception']['values']:
+ if 'value' in entry:
+ entry['value'] = translate_message(entry['value'])
+
+ return data
diff --git a/src/sentry/lang/javascript/plugin.py b/src/sentry/lang/javascript/plugin.py
index 122c0ce4610961..6598a7c9ed38d8 100644
--- a/src/sentry/lang/javascript/plugin.py
+++ b/src/sentry/lang/javascript/plugin.py
@@ -7,12 +7,15 @@
from .processor import JavaScriptStacktraceProcessor
from .errormapping import rewrite_exception
+from .errorlocale import translate_exception
def preprocess_event(data):
rewrite_exception(data)
+ translate_exception(data)
fix_culprit(data)
- inject_device_data(data)
+ if data.get('platform') == 'javascript':
+ inject_device_data(data)
generate_modules(data)
return data
@@ -23,7 +26,7 @@ def generate_modules(data):
for info in find_stacktraces_in_data(data):
for frame in info.stacktrace['frames']:
platform = frame.get('platform') or data['platform']
- if platform != 'javascript' or frame.get('module'):
+ if platform not in ('javascript', 'node') or frame.get('module'):
continue
abs_path = frame.get('abs_path')
if abs_path and abs_path.startswith(('http:', 'https:', 'webpack:', 'app:')):
@@ -129,10 +132,10 @@ def can_configure_for_project(self, project, **kwargs):
def get_event_preprocessors(self, data, **kwargs):
# XXX: rewrite_exception we probably also want if the event
# platform is something else? unsure
- if data.get('platform') == 'javascript':
+ if data.get('platform') in ('javascript', 'node'):
return [preprocess_event]
return []
def get_stacktrace_processors(self, data, stacktrace_infos, platforms, **kwargs):
- if 'javascript' in platforms:
+ if 'javascript' in platforms or 'node' in platforms:
return [JavaScriptStacktraceProcessor]
diff --git a/src/sentry/lang/javascript/processor.py b/src/sentry/lang/javascript/processor.py
index 39583ba38e2458..6415ddf33c7b20 100644
--- a/src/sentry/lang/javascript/processor.py
+++ b/src/sentry/lang/javascript/processor.py
@@ -12,7 +12,7 @@
from os.path import splitext
from requests.utils import get_encoding_from_headers
from six.moves.urllib.parse import urljoin, urlsplit
-from libsourcemap import from_json as view_from_json
+from symbolic import SourceMapView
# In case SSL is unavailable (light builds) we can't import this here.
try:
@@ -56,7 +56,7 @@ class ZeroReturnError(Exception):
VERSION_RE = re.compile(r'^[a-f0-9]{32}|[a-f0-9]{40}$', re.I)
NODE_MODULES_RE = re.compile(r'\bnode_modules/')
SOURCE_MAPPING_URL_RE = re.compile(r'\/\/# sourceMappingURL=(.*)$')
-# the maximum number of remote resources (i.e. sourc eifles) that should be
+# the maximum number of remote resources (i.e. source files) that should be
# fetched
MAX_RESOURCE_FETCHES = 100
@@ -325,10 +325,7 @@ def fetch_file(url, project=None, release=None, dist=None, allow_scraping=True):
verify_ssl = bool(project.get_option('sentry:verify_ssl', False))
token = project.get_option('sentry:token')
if token:
- token_header = project.get_option(
- 'sentry:token_header',
- 'X-Sentry-Token',
- )
+ token_header = project.get_option('sentry:token_header') or 'X-Sentry-Token'
headers[token_header] = token
with metrics.timer('sourcemaps.fetch'):
@@ -336,6 +333,16 @@ def fetch_file(url, project=None, release=None, dist=None, allow_scraping=True):
z_body = zlib.compress(result.body)
cache.set(cache_key, (url, result.headers, z_body, result.status, result.encoding), 60)
+ # If we did not get a 200 OK we just raise a cannot fetch here.
+ if result.status != 200:
+ raise http.CannotFetch(
+ {
+ 'type': EventError.FETCH_INVALID_HTTP_CODE,
+ 'value': result.status,
+ 'url': http.expose_url(url),
+ }
+ )
+
# Make sure the file we're getting back is six.binary_type. The only
# reason it'd not be binary would be from old cached blobs, so
# for compatibility with current cached files, let's coerce back to
@@ -391,7 +398,7 @@ def fetch_sourcemap(url, project=None, release=None, dist=None, allow_scraping=T
)
body = result.body
try:
- return view_from_json(body)
+ return SourceMapView.from_json_bytes(body)
except Exception as exc:
# This is in debug because the product shows an error already.
logger.debug(six.text_type(exc), exc_info=True)
@@ -418,9 +425,6 @@ def generate_module(src):
return UNKNOWN_MODULE
filename, ext = splitext(urlsplit(src).path)
- if ext not in ('.js', '.jsx', '.coffee'):
- return UNKNOWN_MODULE
-
if filename.endswith('.min'):
filename = filename[:-4]
@@ -498,7 +502,7 @@ def preprocess_step(self, processing_task):
def handles_frame(self, frame, stacktrace_info):
platform = frame.get('platform') or self.data.get('platform')
- return (settings.SENTRY_SCRAPE_JAVASCRIPT_CONTEXT and platform == 'javascript')
+ return (settings.SENTRY_SCRAPE_JAVASCRIPT_CONTEXT and platform in ('javascript', 'node'))
def preprocess_frame(self, processable_frame):
# Stores the resolved token. This is used to cross refer to other
@@ -520,13 +524,20 @@ def process_frame(self, processable_frame, processing_task):
if not frame.get('abs_path') or not frame.get('lineno'):
return
+ # can't fetch if this is internal node module as well
+ # therefore we only process user-land frames (starting with /)
+ # or those created by bundle/webpack internals
+ if self.data.get('platform') == 'node' and \
+ not frame.get('abs_path').startswith(('/', 'app:', 'webpack:')):
+ return
+
errors = cache.get_errors(frame['abs_path'])
if errors:
all_errors.extend(errors)
# This might fail but that's okay, we try with a different path a
# bit later down the road.
- source = self.get_source(frame['abs_path'])
+ source = self.get_sourceview(frame['abs_path'])
in_app = None
new_frame = dict(frame)
@@ -549,11 +560,20 @@ def process_frame(self, processable_frame, processing_task):
sourcemap_label = http.expose_url(sourcemap_label)
+ if frame.get('function'):
+ minified_function_name = frame['function']
+ minified_source = self.get_sourceview(frame['abs_path'])
+ else:
+ minified_function_name = minified_source = None
+
try:
# Errors are 1-indexed in the frames, so we need to -1 to get
# zero-indexed value from tokens.
assert frame['lineno'] > 0, "line numbers are 1-indexed"
- token = sourcemap_view.lookup_token(frame['lineno'] - 1, frame['colno'] - 1)
+ token = sourcemap_view.lookup(frame['lineno'] - 1,
+ frame['colno'] - 1,
+ minified_function_name,
+ minified_source)
except Exception:
token = None
all_errors.append(
@@ -580,9 +600,9 @@ def process_frame(self, processable_frame, processing_task):
logger.debug(
'Mapping compressed source %r to mapping in %r', frame['abs_path'], abs_path
)
- source = self.get_source(abs_path)
+ source = self.get_sourceview(abs_path)
- if not source:
+ if source is None:
errors = cache.get_errors(abs_path)
if errors:
all_errors.extend(errors)
@@ -599,17 +619,12 @@ def process_frame(self, processable_frame, processing_task):
new_frame['lineno'] = token.src_line + 1
new_frame['colno'] = token.src_col + 1
- # Find the original function name with a bit of guessing
- original_function_name = None
+ # Try to use the function name we got from symbolic
+ original_function_name = token.function_name
# In the ideal case we can use the function name from the
# frame and the location to resolve the original name
# through the heuristics in our sourcemap library.
- if frame.get('function'):
- minified_source = self.get_source(frame['abs_path'], raw=True)
- original_function_name = sourcemap_view.get_original_function_name(
- token.dst_line, token.dst_col, frame['function'],
- minified_source)
if original_function_name is None:
last_token = None
@@ -638,9 +653,14 @@ def process_frame(self, processable_frame, processing_task):
else:
filename = filename.split('webpack:///', 1)[-1]
- # As noted above, '~/' means they're coming from node_modules,
- # so these are not app dependencies
- if filename.startswith('~/'):
+ # As noted above:
+ # * [js/node] '~/' means they're coming from node_modules, so these are not app dependencies
+ # * [node] sames goes for `./node_modules/`, which is used when bundling node apps
+ # * [node] and webpack, which includes it's own code to bootstrap all modules and its internals
+ # eg. webpack:///webpack/bootstrap, webpack:///external
+ if filename.startswith('~/') or \
+ filename.startswith('./node_modules/') or \
+ not filename.startswith('./'):
in_app = False
# And conversely, local dependencies start with './'
elif filename.startswith('./'):
@@ -650,7 +670,7 @@ def process_frame(self, processable_frame, processing_task):
new_frame['module'] = generate_module(filename)
if abs_path.startswith('app:'):
- if NODE_MODULES_RE.search(filename):
+ if filename and NODE_MODULES_RE.search(filename):
in_app = False
else:
in_app = True
@@ -696,7 +716,7 @@ def process_frame(self, processable_frame, processing_task):
def expand_frame(self, frame, source=None):
if frame.get('lineno') is not None:
if source is None:
- source = self.get_source(frame['abs_path'])
+ source = self.get_sourceview(frame['abs_path'])
if source is None:
logger.debug('No source found for %s', frame['abs_path'])
return False
@@ -707,10 +727,10 @@ def expand_frame(self, frame, source=None):
return True
return False
- def get_source(self, filename, raw=False):
+ def get_sourceview(self, filename):
if filename not in self.cache:
self.cache_source(filename)
- return self.cache.get(filename, raw=raw)
+ return self.cache.get(filename)
def cache_source(self, filename):
sourcemaps = self.sourcemaps
@@ -766,12 +786,12 @@ def cache_source(self, filename):
sourcemaps.add(sourcemap_url, sourcemap_view)
# cache any inlined sources
- for src_id, source in sourcemap_view.iter_sources():
- if sourcemap_view.has_source_contents(src_id):
+ for src_id, source_name in sourcemap_view.iter_sources():
+ source_view = sourcemap_view.get_sourceview(src_id)
+ if source_view is not None:
self.cache.add(
- urljoin(sourcemap_url, source),
- lambda view=sourcemap_view, id=src_id: view.get_source_contents(id),
- None,
+ urljoin(sourcemap_url, source_name),
+ source_view
)
def populate_source_cache(self, frames):
@@ -790,6 +810,10 @@ def populate_source_cache(self, frames):
# a fetch error that may be confusing.
if f['abs_path'] == '':
continue
+ # we cannot fetch any other files than those uploaded by user
+ if self.data.get('platform') == 'node' and \
+ not f.get('abs_path').startswith('app:'):
+ continue
pending_file_list.add(f['abs_path'])
for idx, filename in enumerate(pending_file_list):
@@ -803,5 +827,8 @@ def close(self):
metrics.incr(
'sourcemaps.processed',
amount=len(self.sourcemaps_touched),
- instance=self.project.id
+ skip_internal=True,
+ tags={
+ 'project_id': self.project.id,
+ },
)
diff --git a/src/sentry/lang/native/applecrashreport.py b/src/sentry/lang/native/applecrashreport.py
index df9df95864754a..35e21a6427064d 100644
--- a/src/sentry/lang/native/applecrashreport.py
+++ b/src/sentry/lang/native/applecrashreport.py
@@ -1,12 +1,20 @@
from __future__ import absolute_import
import posixpath
+import re
from sentry.utils.compat import implements_to_string
-from sentry.utils.native import parse_addr
from sentry.constants import NATIVE_UNKNOWN_STRING
+from symbolic import parse_addr
+
REPORT_VERSION = '104'
+WINDOWS_PATH_RE = re.compile(r'^[a-z]:\\', re.IGNORECASE)
+
+
+def package_name(pkg):
+ split = '\\' if WINDOWS_PATH_RE.match(pkg) else '/'
+ return pkg.rsplit(split, 1)[-1]
@implements_to_string
@@ -144,8 +152,7 @@ def _convert_frame_to_apple_string(self, frame, next=None, number=0):
symbol = '[inlined] ' + symbol
return '%s%s%s%s%s' % (
str(number).ljust(4, ' '),
- (frame.get('package') or NATIVE_UNKNOWN_STRING).rsplit(
- '/', 1)[-1].ljust(32, ' '),
+ package_name(frame.get('package') or NATIVE_UNKNOWN_STRING).ljust(32, ' '),
hex(instruction_addr).ljust(20, ' '), symbol, offset
)
@@ -173,7 +180,6 @@ def _convert_debug_meta_to_binary_image_row(self, debug_image):
image_addr = parse_addr(debug_image['image_addr']) + slide_value
return '%s - %s %s %s <%s> %s' % (
hex(image_addr), hex(image_addr + debug_image['image_size'] - 1),
- debug_image['name'].rsplit(
- '/', 1)[-1], self.context['device']['arch'],
+ package_name(debug_image['name']), self.context['device']['arch'],
debug_image['uuid'].replace('-', '').lower(), debug_image['name']
)
diff --git a/src/sentry/lang/native/plugin.py b/src/sentry/lang/native/plugin.py
index e5ca040876ba0b..d8f26f7608127f 100644
--- a/src/sentry/lang/native/plugin.py
+++ b/src/sentry/lang/native/plugin.py
@@ -1,9 +1,11 @@
from __future__ import absolute_import
+import six
import logging
import posixpath
-from symsynd import find_best_instruction, parse_addr, ImageLookup
+from symbolic import parse_addr, find_best_instruction, arch_get_ip_reg_name, \
+ ObjectLookup
from sentry import options
from django.db import transaction, IntegrityError
@@ -20,21 +22,23 @@
logger = logging.getLogger(__name__)
-FRAME_CACHE_VERSION = 5
+FRAME_CACHE_VERSION = 6
class NativeStacktraceProcessor(StacktraceProcessor):
+ supported_platforms = ('cocoa', 'native')
+
def __init__(self, *args, **kwargs):
StacktraceProcessor.__init__(self, *args, **kwargs)
debug_meta = self.data.get('debug_meta')
- self.cpu_name = cpu_name_from_data(self.data)
+ self.arch = cpu_name_from_data(self.data)
self.sym = None
self.dsyms_referenced = set()
if debug_meta:
self.available = True
self.debug_meta = debug_meta
self.sdk_info = get_sdk_from_event(self.data)
- self.image_lookup = ImageLookup(
+ self.object_lookup = ObjectLookup(
[img for img in self.debug_meta['images'] if img['type'] == 'apple']
)
else:
@@ -44,23 +48,28 @@ def close(self):
StacktraceProcessor.close(self)
if self.dsyms_referenced:
metrics.incr(
- 'dsyms.processed', amount=len(self.dsyms_referenced), instance=self.project.id
+ 'dsyms.processed',
+ amount=len(self.dsyms_referenced),
+ skip_internal=True,
+ tags={
+ 'project_id': self.project.id,
+ },
)
- if self.sym is not None:
- self.sym.close()
- self.sym = None
def find_best_instruction(self, processable_frame):
"""Given a frame, stacktrace info and frame index this returns the
interpolated instruction address we then use for symbolication later.
"""
- if self.cpu_name is None:
+ if self.arch is None:
return parse_addr(processable_frame['instruction_addr'])
- meta = None
+
+ crashing_frame = False
+ signal = None
+ ip_reg = None
# We only need to provide meta information for frame zero
if processable_frame.idx == 0:
- # The signal is useful information for symsynd in some situations
+ # The signal is useful information for symbolic in some situations
# to disambiugate the first frame. If we can get this information
# from the mechanism we want to pass it onwards.
signal = None
@@ -69,44 +78,50 @@ def find_best_instruction(self, processable_frame):
mechanism = exc['values'][0].get('mechanism')
if mechanism and 'posix_signal' in mechanism and \
'signal' in mechanism['posix_signal']:
- signal = mechanism['posix_signal']['signal']
- meta = {
- 'frame_number': 0,
- 'registers': processable_frame.stacktrace_info.stacktrace.get('registers'),
- 'signal': signal,
- }
+ signal = int(mechanism['posix_signal']['signal'])
+ registers = processable_frame.stacktrace_info.stacktrace.get(
+ 'registers')
+ if registers:
+ ip_reg_name = arch_get_ip_reg_name(self.arch)
+ if ip_reg_name:
+ ip_reg = registers.get(ip_reg_name)
+ crashing_frame = True
return find_best_instruction(
- processable_frame['instruction_addr'], self.cpu_name, meta=meta
+ processable_frame['instruction_addr'],
+ arch=self.arch,
+ crashing_frame=crashing_frame,
+ signal=signal,
+ ip_reg=ip_reg
)
def handles_frame(self, frame, stacktrace_info):
platform = frame.get('platform') or self.data.get('platform')
- return (platform == 'cocoa' and self.available and 'instruction_addr' in frame)
+ return (platform in self.supported_platforms and self.available
+ and 'instruction_addr' in frame)
def preprocess_frame(self, processable_frame):
instr_addr = self.find_best_instruction(processable_frame)
- img = self.image_lookup.find_image(instr_addr)
+ obj = self.object_lookup.find_object(instr_addr)
processable_frame.data = {
'instruction_addr': instr_addr,
- 'image': img,
- 'image_uuid': img['uuid'] if img is not None else None,
+ 'obj': obj,
+ 'obj_uuid': six.text_type(obj.uuid) if obj is not None else None,
'symbolserver_match': None,
}
- if img is not None:
+ if obj is not None:
processable_frame.set_cache_key_from_values(
(
FRAME_CACHE_VERSION,
# Because the images can move around, we want to rebase
# the address for the cache key to be within the image
# the same way as we do it in the symbolizer.
- rebase_addr(instr_addr, img),
- img['uuid'].lower(),
- img['cpu_type'],
- img['cpu_subtype'],
- img['image_size'],
+ rebase_addr(instr_addr, obj),
+ six.text_type(obj.uuid),
+ obj.arch,
+ obj.size,
)
)
@@ -115,19 +130,20 @@ def preprocess_step(self, processing_task):
return False
referenced_images = set(
- pf.data['image_uuid'] for pf in processing_task.iter_processable_frames(self)
- if pf.cache_value is None and pf.data['image_uuid'] is not None
+ pf.data['obj_uuid'] for pf in processing_task.iter_processable_frames(self)
+ if pf.cache_value is None and pf.data['obj_uuid'] is not None
)
- def on_referenced(dsym_file):
- app_info = version_build_from_data(self.data)
- if app_info is not None:
+ app_info = version_build_from_data(self.data)
+ if app_info is not None:
+ def on_referenced(dsym_file):
dsym_app = DSymApp.objects.create_or_update_app(
sync_id=None,
app_id=app_info.id,
project=self.project,
data={'name': app_info.name},
platform=DSymPlatform.APPLE,
+ no_fetch=True
)
try:
with transaction.atomic():
@@ -142,11 +158,12 @@ def on_referenced(dsym_file):
# support one app per dsym file. Since this can
# happen in some cases anyways we ignore it.
pass
+ else:
+ on_referenced = None
self.sym = Symbolizer(
self.project,
- self.image_lookup,
- cpu_name=self.cpu_name,
+ self.object_lookup,
referenced_images=referenced_images,
on_dsym_file_referenced=on_referenced
)
@@ -158,15 +175,15 @@ def fetch_system_symbols(self, processing_task):
to_lookup = []
pf_list = []
for pf in processing_task.iter_processable_frames(self):
- img = pf.data['image']
- if pf.cache_value is not None or img is None or \
- self.sym.is_image_from_app_bundle(img):
+ obj = pf.data['obj']
+ if pf.cache_value is not None or obj is None or \
+ self.sym.is_image_from_app_bundle(obj):
continue
to_lookup.append(
{
- 'object_uuid': img['uuid'],
- 'object_name': img['name'],
- 'addr': '0x%x' % rebase_addr(pf.data['instruction_addr'], img)
+ 'object_uuid': six.text_type(obj.uuid),
+ 'object_name': obj.name or '',
+ 'addr': '0x%x' % rebase_addr(pf.data['instruction_addr'], obj)
}
)
pf_list.append(pf)
@@ -174,7 +191,7 @@ def fetch_system_symbols(self, processing_task):
if not to_lookup:
return
- rv = lookup_system_symbols(to_lookup, self.sdk_info, self.sym.cpu_name)
+ rv = lookup_system_symbols(to_lookup, self.sdk_info, self.arch)
if rv is not None:
for symrv, pf in zip(rv, pf_list):
if symrv is None:
@@ -183,22 +200,29 @@ def fetch_system_symbols(self, processing_task):
def process_frame(self, processable_frame, processing_task):
frame = processable_frame.frame
+ raw_frame = dict(frame)
errors = []
- new_frames = []
- raw_frame = dict(frame)
if processable_frame.cache_value is None:
# Construct a raw frame that is used by the symbolizer
# backend. We only assemble the bare minimum we need here.
instruction_addr = processable_frame.data['instruction_addr']
- in_app = self.sym.is_in_app(instruction_addr, sdk_info=self.sdk_info)
+ in_app = self.sym.is_in_app(
+ instruction_addr,
+ sdk_info=self.sdk_info
+ )
+
if in_app and raw_frame.get('function') is not None:
- in_app = not self.sym.is_internal_function(raw_frame['function'])
+ in_app = not self.sym.is_internal_function(
+ raw_frame['function'])
+
if raw_frame.get('in_app') is None:
raw_frame['in_app'] = in_app
- img_uuid = processable_frame.data['image_uuid']
- if img_uuid is not None:
- self.dsyms_referenced.add(img_uuid)
+
+ obj_uuid = processable_frame.data['obj_uuid']
+ if obj_uuid is not None:
+ self.dsyms_referenced.add(obj_uuid)
+
try:
symbolicated_frames = self.sym.symbolize_frame(
instruction_addr,
@@ -216,12 +240,7 @@ def process_frame(self, processable_frame, processing_task):
scope='native',
object='dsym:%s' % e.image_uuid,
type=e.type,
- data={
- 'image_path': e.image_path,
- 'image_uuid': e.image_uuid,
- 'image_arch': e.image_arch,
- 'message': e.message,
- }
+ data=e.get_data()
)
# This in many ways currently does not really do anything.
@@ -234,24 +253,20 @@ def process_frame(self, processable_frame, processing_task):
# optional dsyms)
errors = []
if e.is_user_fixable or e.is_sdk_failure:
- errors.append(
- {
- 'type': e.type,
- 'image_uuid': e.image_uuid,
- 'image_path': e.image_path,
- 'image_arch': e.image_arch,
- 'message': e.message,
- }
- )
+ errors.append(e.get_data())
else:
- logger.debug('Failed to symbolicate with native backend', exc_info=True)
+ logger.debug('Failed to symbolicate with native backend',
+ exc_info=True)
+
return [raw_frame], [raw_frame], errors
processable_frame.set_cache_value([in_app, symbolicated_frames])
- else:
+
+ else: # processable_frame.cache_value is present
in_app, symbolicated_frames = processable_frame.cache_value
raw_frame['in_app'] = in_app
+ new_frames = []
for sfrm in symbolicated_frames:
new_frame = dict(frame)
new_frame['function'] = sfrm['function']
@@ -259,16 +274,18 @@ def process_frame(self, processable_frame, processing_task):
new_frame['symbol'] = sfrm['symbol']
new_frame['abs_path'] = sfrm['abs_path']
new_frame['filename'] = sfrm.get('filename') or \
- (sfrm['abs_path'] and posixpath.basename(sfrm['abs_path'])) or None
+ (sfrm['abs_path'] and posixpath.basename(sfrm['abs_path'])) or \
+ None
if sfrm.get('lineno'):
new_frame['lineno'] = sfrm['lineno']
if sfrm.get('colno'):
new_frame['colno'] = sfrm['colno']
- if sfrm.get('package'):
- new_frame['package'] = sfrm['package']
+ if sfrm.get('package') or processable_frame.data['obj'] is not None:
+ new_frame['package'] = sfrm.get(
+ 'package', processable_frame.data['obj'].name)
if new_frame.get('in_app') is None:
- new_frame['in_app'
- ] = (in_app and not self.sym.is_internal_function(new_frame['function']))
+ new_frame['in_app'] = in_app and \
+ not self.sym.is_internal_function(new_frame['function'])
new_frames.append(new_frame)
return new_frames, [raw_frame], []
@@ -278,5 +295,5 @@ class NativePlugin(Plugin2):
can_disable = False
def get_stacktrace_processors(self, data, stacktrace_infos, platforms, **kwargs):
- if 'cocoa' in platforms:
+ if any(platform in NativeStacktraceProcessor.supported_platforms for platform in platforms):
return [NativeStacktraceProcessor]
diff --git a/src/sentry/lang/native/symbolizer.py b/src/sentry/lang/native/symbolizer.py
index 344cfc4445df49..854c40bcf91595 100644
--- a/src/sentry/lang/native/symbolizer.py
+++ b/src/sentry/lang/native/symbolizer.py
@@ -3,12 +3,12 @@
import re
import six
-from symsynd import demangle_symbol, SymbolicationError, get_cpu_name, \
- ImageLookup, Symbolizer as SymsyndSymbolizer
+from symbolic import SymbolicError, ObjectLookup, LineInfo, parse_addr
from sentry.utils.safe import trim
from sentry.utils.compat import implements_to_string
from sentry.models import EventError, ProjectDSymFile
+from sentry.lang.native.utils import rebase_addr
from sentry.constants import MAX_SYM, NATIVE_UNKNOWN_STRING
FATAL_ERRORS = (EventError.NATIVE_MISSING_DSYM, EventError.NATIVE_BAD_DSYM, )
@@ -31,8 +31,15 @@
SIM_PATH = '/Developer/CoreSimulator/Devices/'
SIM_APP_PATH = '/Containers/Bundle/Application/'
MAC_OS_PATH = '.app/Contents/'
+LINUX_SYS_PATHS = (
+ '/lib/',
+ '/usr/lib/',
+ 'linux-gate.so',
+)
+WINDOWS_SYS_PATH = re.compile(r'^[a-z]:\\windows', re.IGNORECASE)
-_internal_function_re = re.compile(r'(kscm_|kscrash_|KSCrash |SentryClient |RNSentry )')
+_internal_function_re = re.compile(
+ r'(kscm_|kscrash_|KSCrash |SentryClient |RNSentry )')
KNOWN_GARBAGE_SYMBOLS = set([
'_mh_execute_header',
@@ -45,19 +52,20 @@
class SymbolicationFailed(Exception):
message = None
- def __init__(self, message=None, type=None, image=None):
+ def __init__(self, message=None, type=None, obj=None):
Exception.__init__(self)
self.message = six.text_type(message)
self.type = type
- if image is not None:
- self.image_uuid = image['uuid'].lower()
- self.image_path = image['name']
- self.image_name = image['name'].rsplit('/', 1)[-1]
- self.image_arch = get_cpu_name(image['cpu_type'], image['cpu_subtype'])
+ self.image_name = None
+ self.image_path = None
+ if obj is not None:
+ self.image_uuid = six.text_type(obj.uuid)
+ if obj.name:
+ self.image_path = obj.name
+ self.image_name = obj.name.rsplit('/', 1)[-1]
+ self.image_arch = obj.arch
else:
self.image_uuid = None
- self.image_name = None
- self.image_path = None
self.image_arch = None
@property
@@ -75,6 +83,17 @@ def is_sdk_failure(self):
"""An error that most likely happened because of a bad SDK."""
return self.type == EventError.NATIVE_UNKNOWN_IMAGE
+ def get_data(self):
+ """Returns the event data."""
+ rv = {'message': self.message, 'type': self.type}
+ if self.image_path is not None:
+ rv['image_path'] = self.image_path
+ if self.image_uuid is not None:
+ rv['image_uuid'] = self.image_uuid
+ if self.image_arch is not None:
+ rv['image_arch'] = self.image_arch
+ return rv
+
def __str__(self):
rv = []
if self.type is not None:
@@ -88,41 +107,30 @@ def __str__(self):
class Symbolizer(object):
- """This symbolizer dispatches to both symsynd and the system symbols
+ """This symbolizer dispatches to both symbolic and the system symbols
we have in the database and reports errors slightly differently.
"""
- def __init__(
- self,
- project,
- binary_images,
- referenced_images=None,
- cpu_name=None,
- on_dsym_file_referenced=None
- ):
- if isinstance(binary_images, ImageLookup):
- self.image_lookup = binary_images
- else:
- self.image_lookup = ImageLookup(binary_images)
-
- self._symbolizer = SymsyndSymbolizer()
-
- to_load = referenced_images
- if to_load is None:
- to_load = self.image_lookup.get_uuids()
-
- self.dsym_paths = ProjectDSymFile.dsymcache.fetch_dsyms(
- project, to_load, on_dsym_file_referenced=on_dsym_file_referenced
- )
-
- self.cpu_name = cpu_name
-
- def close(self):
- self._symbolizer.close()
-
- def _process_frame(self, frame, img):
- symbol = trim(frame['symbol'], MAX_SYM)
- function = trim(demangle_symbol(frame['symbol'], simplified=True), MAX_SYM)
+ def __init__(self, project, object_lookup, referenced_images,
+ on_dsym_file_referenced=None):
+ if not isinstance(object_lookup, ObjectLookup):
+ object_lookup = ObjectLookup(object_lookup)
+ self.object_lookup = object_lookup
+
+ self.symcaches, self.symcaches_conversion_errors = \
+ ProjectDSymFile.dsymcache.get_symcaches(
+ project, referenced_images,
+ on_dsym_file_referenced=on_dsym_file_referenced,
+ with_conversion_errors=True)
+
+ def _process_frame(self, sym, obj, package=None, addr_off=0):
+ frame = {
+ 'sym_addr': sym.sym_addr + addr_off,
+ 'instruction_addr': sym.instr_addr + addr_off,
+ 'lineno': sym.line,
+ }
+ symbol = trim(sym.symbol, MAX_SYM)
+ function = trim(sym.function_name, MAX_SYM)
frame['function'] = function
if function != symbol:
@@ -130,105 +138,119 @@ def _process_frame(self, frame, img):
else:
frame['symbol'] = None
- frame['filename'] = trim(frame.get('filename'), 256)
- frame['abs_path'] = trim(frame.get('abs_path'), 256)
+ frame['filename'] = trim(sym.rel_path, 256)
+ frame['abs_path'] = trim(sym.abs_path, 256)
+ if package is not None:
+ frame['package'] = package
return frame
- def is_image_from_app_bundle(self, img, sdk_info=None):
- fn = img['name']
- is_mac_platform = (sdk_info is not None and sdk_info['sdk_name'].lower() == 'macos')
- if not (
- fn.startswith(APP_BUNDLE_PATHS) or (SIM_PATH in fn and SIM_APP_PATH in fn) or
- (is_mac_platform and MAC_OS_PATH in fn)
- ):
+ def is_image_from_app_bundle(self, obj, sdk_info=None):
+ obj_path = obj.name
+ if not obj_path:
return False
- return True
- def _is_support_framework(self, img):
+ if obj_path.startswith(APP_BUNDLE_PATHS):
+ return True
+
+ if SIM_PATH in obj_path and SIM_APP_PATH in obj_path:
+ return True
+
+ sdk_name = sdk_info['sdk_name'].lower() if sdk_info else ''
+ if sdk_name == 'macos' and MAC_OS_PATH in obj_path:
+ return True
+ if sdk_name == 'linux' and not obj_path.startswith(LINUX_SYS_PATHS):
+ return True
+ if sdk_name == 'windows' and not WINDOWS_SYS_PATH.match(obj_path):
+ return True
+
+ return False
+
+ def _is_support_framework(self, obj):
"""True if the frame is from a framework that is known and app
bundled. Those are frameworks which are specifically not frameworks
that are ever in_app.
"""
- return _support_framework.search(img['name']) is not None
+ return obj.name and _support_framework.search(obj.name) is not None
- def _is_app_bundled_framework(self, img):
- fn = img['name']
- return fn.startswith(APP_BUNDLE_PATHS) and '/Frameworks/' in fn
+ def _is_app_bundled_framework(self, obj):
+ fn = obj.name
+ return fn and fn.startswith(APP_BUNDLE_PATHS) and '/Frameworks/' in fn
- def _is_app_frame(self, instruction_addr, img, sdk_info=None):
+ def _is_app_frame(self, instruction_addr, obj, sdk_info=None):
"""Given a frame derives the value of `in_app` by discarding the
original value of the frame.
"""
# Anything that is outside the app bundle is definitely not a
# frame from out app.
- if not self.is_image_from_app_bundle(img, sdk_info=sdk_info):
+ if not self.is_image_from_app_bundle(obj, sdk_info=sdk_info):
return False
# We also do not consider known support frameworks to be part of
# the app
- if self._is_support_framework(img):
+ if self._is_support_framework(obj):
return False
# Otherwise, yeah, let's just say it's in_app
return True
- def _is_optional_dsym(self, img, sdk_info=None):
+ def _is_optional_dsym(self, obj, sdk_info=None):
"""Checks if this is a dsym that is optional."""
# Frames that are not in the app are not considered optional. In
# theory we should never reach this anyways.
- if not self.is_image_from_app_bundle(img, sdk_info=sdk_info):
+ if not self.is_image_from_app_bundle(obj, sdk_info=sdk_info):
return False
# If we're dealing with an app bundled framework that is also
# considered optional.
- if self._is_app_bundled_framework(img):
+ if self._is_app_bundled_framework(obj):
return True
# Frameworks that are known to sentry and bundled helpers are always
# optional for now. In theory this should always be False here
# because we should catch it with the last branch already.
- if self._is_support_framework(img):
+ if self._is_support_framework(obj):
return True
return False
- def _is_simulator_frame(self, frame, img):
- return _sim_platform_re.search(img['name']) is not None
-
- def _symbolize_app_frame(self, instruction_addr, img, sdk_info=None):
- dsym_path = self.dsym_paths.get(img['uuid'])
- if dsym_path is None:
- if self._is_optional_dsym(img, sdk_info=sdk_info):
+ def _is_simulator_frame(self, frame, obj):
+ return obj.name and _sim_platform_re.search(obj.name) is not None
+
+ def _symbolize_app_frame(self, instruction_addr, obj, sdk_info=None):
+ symcache = self.symcaches.get(obj.uuid)
+ if symcache is None:
+ # In case we know what error happened on symcache conversion
+ # we can report it to the user now.
+ if obj.uuid in self.symcaches_conversion_errors:
+ raise SymbolicationFailed(
+ message=self.symcaches_conversion_errors[obj.uuid],
+ type=EventError.NATIVE_BAD_DSYM,
+ obj=obj
+ )
+ if self._is_optional_dsym(obj, sdk_info=sdk_info):
type = EventError.NATIVE_MISSING_OPTIONALLY_BUNDLED_DSYM
else:
type = EventError.NATIVE_MISSING_DSYM
- raise SymbolicationFailed(type=type, image=img)
-
- # cputype of image might be a variation of self.cpu_name
- # e.g.: armv7 instead of armv7f
- # (example error fat file does not contain armv7f)
- cpu_name = get_cpu_name(img['cpu_type'], img['cpu_subtype'])
+ raise SymbolicationFailed(type=type, obj=obj)
try:
- rv = self._symbolizer.symbolize(
- dsym_path,
- img['image_vmaddr'],
- img['image_addr'],
- instruction_addr,
- cpu_name,
- symbolize_inlined=True
- )
- except SymbolicationError as e:
+ rv = symcache.lookup(rebase_addr(instruction_addr, obj))
+ except SymbolicError as e:
raise SymbolicationFailed(
- type=EventError.NATIVE_BAD_DSYM, message=six.text_type(e), image=img
+ type=EventError.NATIVE_BAD_DSYM, message=six.text_type(e), obj=obj
)
if not rv:
- raise SymbolicationFailed(type=EventError.NATIVE_MISSING_SYMBOL, image=img)
- return [self._process_frame(nf, img) for nf in reversed(rv)]
+ # For some frameworks we are willing to ignore missing symbol
+ # errors.
+ if self._is_optional_dsym(obj, sdk_info=sdk_info):
+ return []
+ raise SymbolicationFailed(
+ type=EventError.NATIVE_MISSING_SYMBOL, obj=obj)
+ return [self._process_frame(s, obj, addr_off=obj.addr) for s in reversed(rv)]
- def _convert_symbolserver_match(self, instruction_addr, symbolserver_match, img):
+ def _convert_symbolserver_match(self, instruction_addr, symbolserver_match, obj):
"""Symbolizes a frame with system symbols only."""
if symbolserver_match is None:
return []
@@ -238,41 +260,31 @@ def _convert_symbolserver_match(self, instruction_addr, symbolserver_match, img)
symbol = symbol[1:]
return [
- self._process_frame(
- dict(
- symbol=symbol,
- filename=None,
- abs_path=None,
- lineno=0,
- colno=0,
- package=symbolserver_match['object_name']
- ), img
- )
+ self._process_frame(LineInfo(
+ sym_addr=parse_addr(symbolserver_match['addr']),
+ instr_addr=parse_addr(instruction_addr),
+ line=None,
+ lang=None,
+ symbol=symbol,
+ ), obj, package=symbolserver_match['object_name'])
]
def symbolize_frame(self, instruction_addr, sdk_info=None, symbolserver_match=None):
- # If we do not have a CPU name we fail. We currently only support
- # a single cpu architecture.
- if self.cpu_name is None:
- raise SymbolicationFailed(
- type=EventError.NATIVE_INTERNAL_FAILURE, message='Found multiple architectures.'
- )
-
- img = self.image_lookup.find_image(instruction_addr)
- if img is None:
+ obj = self.object_lookup.find_object(instruction_addr)
+ if obj is None:
raise SymbolicationFailed(type=EventError.NATIVE_UNKNOWN_IMAGE)
# If we are dealing with a frame that is not bundled with the app
# we look at system symbols. If that fails, we go to looking for
# app symbols explicitly.
- if not self.is_image_from_app_bundle(img, sdk_info=sdk_info):
- return self._convert_symbolserver_match(instruction_addr, symbolserver_match, img)
+ if not self.is_image_from_app_bundle(obj, sdk_info=sdk_info):
+ return self._convert_symbolserver_match(instruction_addr, symbolserver_match, obj)
- return self._symbolize_app_frame(instruction_addr, img, sdk_info=sdk_info)
+ return self._symbolize_app_frame(instruction_addr, obj, sdk_info=sdk_info)
def is_in_app(self, instruction_addr, sdk_info=None):
- img = self.image_lookup.find_image(instruction_addr)
- return img is not None and self._is_app_frame(instruction_addr, img, sdk_info=sdk_info)
+ obj = self.object_lookup.find_object(instruction_addr)
+ return obj is not None and self._is_app_frame(instruction_addr, obj, sdk_info=sdk_info)
def is_internal_function(self, function):
return _internal_function_re.search(function) is not None
diff --git a/src/sentry/lang/native/utils.py b/src/sentry/lang/native/utils.py
index 78a8add8eb003e..64404d930f4bbe 100644
--- a/src/sentry/lang/native/utils.py
+++ b/src/sentry/lang/native/utils.py
@@ -1,10 +1,12 @@
from __future__ import absolute_import
+import re
import six
import logging
from collections import namedtuple
-from symsynd import get_cpu_name, parse_addr
+from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile
+from symbolic import parse_addr, arch_from_breakpad, arch_from_macho, arch_is_known, ProcessState
from sentry.interfaces.contexts import DeviceContextType
@@ -17,6 +19,15 @@
'watchOS': 'macho',
}
+# Regular expression to parse OS versions from a minidump OS string
+VERSION_RE = re.compile(r'(\d+\.\d+\.\d+)\s+(.*)')
+
+# Mapping of well-known minidump OS constants to our internal names
+MINIDUMP_OS_TYPES = {
+ 'Mac OS X': 'macOS',
+ 'Windows NT': 'Windows',
+}
+
AppInfo = namedtuple('AppInfo', ['id', 'version', 'build', 'name'])
@@ -67,7 +78,8 @@ def get_sdk_from_os(data):
if 'name' not in data or 'version' not in data:
return
try:
- system_version = tuple(int(x) for x in (data['version'] + '.0' * 3).split('.')[:3])
+ version = data['version'].split('-', 1)[0] + '.0' * 3
+ system_version = tuple(int(x) for x in version.split('.')[:3])
except ValueError:
return
@@ -92,7 +104,13 @@ def cpu_name_from_data(data):
unique_cpu_name = None
images = (data.get('debug_meta') or {}).get('images') or []
for img in images:
- cpu_name = get_cpu_name(img['cpu_type'], img['cpu_subtype'])
+ if img.get('arch') and arch_is_known(img['arch']):
+ cpu_name = img['arch']
+ elif img.get('cpu_type') is not None \
+ and img.get('cpu_subtype') is not None:
+ cpu_name = arch_from_macho(img['cpu_type'], img['cpu_subtype'])
+ else:
+ cpu_name = None
if unique_cpu_name is None:
unique_cpu_name = cpu_name
elif unique_cpu_name != cpu_name:
@@ -119,8 +137,8 @@ def version_build_from_data(data):
return None
-def rebase_addr(instr_addr, img):
- return parse_addr(instr_addr) - parse_addr(img['image_addr'])
+def rebase_addr(instr_addr, obj):
+ return parse_addr(instr_addr) - parse_addr(obj.addr)
def sdk_info_to_sdk_id(sdk_info):
@@ -134,3 +152,70 @@ def sdk_info_to_sdk_id(sdk_info):
if build is not None:
rv = '%s_%s' % (rv, build)
return rv
+
+
+def merge_minidump_event(data, minidump):
+ if isinstance(minidump, InMemoryUploadedFile):
+ state = ProcessState.from_minidump_buffer(minidump.read())
+ elif isinstance(minidump, TemporaryUploadedFile):
+ state = ProcessState.from_minidump(minidump.temporary_file_path())
+ else:
+ state = ProcessState.from_minidump(minidump.name)
+
+ data['level'] = 'fatal' if state.crashed else 'info'
+ data['message'] = 'Assertion Error: %s' % state.assertion if state.assertion \
+ else 'Fatal Error: %s' % state.crash_reason
+
+ if state.timestamp:
+ data['timestamp'] = float(state.timestamp)
+
+ # Extract as much context information as we can.
+ info = state.system_info
+ context = data.setdefault('contexts', {})
+ os = context.setdefault('os', {})
+ device = context.setdefault('device', {})
+ os['type'] = 'os' # Required by "get_sdk_from_event"
+ os['name'] = MINIDUMP_OS_TYPES.get(info.os_name, info.os_name)
+ os['version'] = info.os_version
+ os['build'] = info.os_build
+ device['arch'] = arch_from_breakpad(info.cpu_family)
+
+ # We can extract stack traces here already but since CFI is not
+ # available yet (without debug symbols), the stackwalker will
+ # resort to stack scanning which yields low-quality results. If
+ # the user provides us with debug symbols, we could reprocess this
+ # minidump and add improved stacktraces later.
+ data['threads'] = [{
+ 'id': thread.thread_id,
+ 'crashed': False,
+ 'stacktrace': {
+ 'frames': [{
+ 'instruction_addr': '0x%x' % frame.return_address,
+ 'function': '', # Required by interface
+ 'package': frame.module.name if frame.module else None,
+ } for frame in reversed(list(thread.frames()))],
+ },
+ } for thread in state.threads()]
+
+ # Mark the crashed thread and add its stacktrace to the exception
+ crashed_thread = data['threads'][state.requesting_thread]
+ crashed_thread['crashed'] = True
+
+ # Extract the crash reason and infos
+ data['exception'] = {
+ 'value': data['message'],
+ 'thread_id': crashed_thread['id'],
+ 'type': state.crash_reason,
+ # Move stacktrace here from crashed_thread (mutating!)
+ 'stacktrace': crashed_thread.pop('stacktrace'),
+ }
+
+ # Extract referenced (not all loaded) images
+ images = [{
+ 'type': 'apple', # Required by interface
+ 'uuid': six.text_type(module.uuid),
+ 'image_addr': '0x%x' % module.addr,
+ 'image_size': '0x%x' % module.size,
+ 'name': module.name,
+ } for module in state.modules()]
+ data.setdefault('debug_meta', {})['images'] = images
diff --git a/src/sentry/lint/engine.py b/src/sentry/lint/engine.py
index 6219309ecaa24b..74f8e1617857d1 100644
--- a/src/sentry/lint/engine.py
+++ b/src/sentry/lint/engine.py
@@ -1,6 +1,6 @@
"""
Our linter engine needs to run in 3 different scenarios:
- * Linting all files (python and js)
+ * Linting all files (python, js, less)
* Linting only python files (--python)
* Linting only js files (--js)
@@ -35,6 +35,19 @@ def register_checks():
register_checks()
+def get_project_root():
+ return os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
+
+
+def get_node_modules_bin(name):
+ return os.path.join(
+ get_project_root(), 'node_modules', '.bin', name)
+
+
+def get_prettier_path():
+ return get_node_modules_bin('prettier')
+
+
def get_files(path):
results = []
for root, _, files in os.walk(path):
@@ -44,8 +57,11 @@ def get_files(path):
def get_modified_files(path):
- return [s for s in check_output(
- ['git', 'diff-index', '--cached', '--name-only', 'HEAD']).split('\n') if s]
+ return [
+ s
+ for s in check_output(['git', 'diff-index', '--cached', '--name-only', 'HEAD']).split('\n')
+ if s
+ ]
def get_files_for_list(file_list):
@@ -71,6 +87,13 @@ def get_js_files(file_list=None):
]
+def get_less_files(file_list=None):
+ if file_list is None:
+ file_list = ['src/sentry/static/sentry/less', 'src/sentry/static/sentry/app']
+ return [x for x in get_files_for_list(file_list) if x.endswith(('.less'))]
+ return file_list
+
+
def get_python_files(file_list=None):
if file_list is None:
file_list = ['src', 'tests']
@@ -80,7 +103,8 @@ def get_python_files(file_list=None):
]
-def py_lint(file_list):
+# parseable is a no-op
+def py_lint(file_list, parseable=False):
from flake8.engine import get_style_guide
file_list = get_python_files(file_list)
@@ -90,32 +114,30 @@ def py_lint(file_list):
return report.total_errors != 0
-def js_lint(file_list=None):
+def js_lint(file_list=None, parseable=False, format=False):
- project_root = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir,
- os.pardir)
- eslint_path = os.path.join(project_root, 'node_modules', '.bin', 'eslint')
+ eslint_path = get_node_modules_bin('eslint')
if not os.path.exists(eslint_path):
from click import echo
echo('!! Skipping JavaScript linting because eslint is not installed.')
return False
- eslint_config = os.path.join(project_root, '.eslintrc')
js_file_list = get_js_files(file_list)
has_errors = False
if js_file_list:
- status = Popen([eslint_path, '--config', eslint_config, '--ext', '.jsx', '--fix']
- + js_file_list).wait()
+ cmd = [eslint_path, '--ext', '.js,.jsx']
+ if format:
+ cmd.append('--fix')
+ if parseable:
+ cmd.append('--format=checkstyle')
+ status = Popen(cmd + js_file_list).wait()
has_errors = status != 0
return has_errors
-PRETTIER_VERSION = "1.2.2"
-
-
def yarn_check(file_list):
"""
Checks if package.json was modified WITHOUT a corresponding change in the Yarn
@@ -141,16 +163,7 @@ def yarn_check(file_list):
return False
-def js_format(file_list=None):
- """
- We only format JavaScript code as part of this pre-commit hook. It is not part
- of the lint engine.
- """
- project_root = os.path.join(os.path.dirname(
- __file__), os.pardir, os.pardir, os.pardir)
- prettier_path = os.path.join(
- project_root, 'node_modules', '.bin', 'prettier')
-
+def is_prettier_valid(project_root, prettier_path):
if not os.path.exists(prettier_path):
echo('[sentry.lint] Skipping JavaScript formatting because prettier is not installed.', err=True)
return False
@@ -176,6 +189,20 @@ def js_format(file_list=None):
err=True)
return False
+ return True
+
+
+def js_format(file_list=None):
+ """
+ We only format JavaScript code as part of this pre-commit hook. It is not part
+ of the lint engine.
+ """
+ project_root = get_project_root()
+ prettier_path = get_prettier_path()
+
+ if not is_prettier_valid(project_root, prettier_path):
+ return False
+
js_file_list = get_js_files(file_list)
# manually exclude some bad files
@@ -183,10 +210,7 @@ def js_format(file_list=None):
return run_formatter([prettier_path,
'--write',
- '--single-quote',
- '--bracket-spacing=false',
- '--print-width=90',
- '--jsx-bracket-same-line=true'],
+ ],
js_file_list)
@@ -194,9 +218,7 @@ def js_test(file_list=None):
"""
Run JavaScript unit tests on relevant files ONLY as part of pre-commit hook
"""
- project_root = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir,
- os.pardir)
- jest_path = os.path.join(project_root, 'node_modules', '.bin', 'jest')
+ jest_path = get_node_modules_bin('jest')
if not os.path.exists(jest_path):
from click import echo
@@ -213,6 +235,26 @@ def js_test(file_list=None):
return has_errors
+def less_format(file_list=None):
+ """
+ We only format less code as part of this pre-commit hook. It is not part
+ of the lint engine.
+ """
+ project_root = get_project_root()
+ prettier_path = get_prettier_path()
+
+ if not is_prettier_valid(project_root, prettier_path):
+ return False
+
+ less_file_list = get_less_files(file_list)
+ return run_formatter(
+ [
+ prettier_path,
+ '--write',
+ ], less_file_list
+ )
+
+
def py_format(file_list=None):
try:
__import__('autopep8')
@@ -260,7 +302,8 @@ def run_formatter(cmd, file_list, prompt_on_changes=True):
return has_errors
-def run(file_list=None, format=True, lint=True, js=True, py=True, yarn=True, test=False):
+def run(file_list=None, format=True, lint=True, js=True, py=True,
+ less=True, yarn=True, test=False, parseable=False):
# pep8.py uses sys.argv to find setup.cfg
old_sysargv = sys.argv
@@ -284,6 +327,8 @@ def run(file_list=None, format=True, lint=True, js=True, py=True, yarn=True, tes
results.append(py_format(file_list))
if js:
results.append(js_format(file_list))
+ if less:
+ results.append(less_format(file_list))
# bail early if a formatter failed
if any(results):
@@ -291,9 +336,9 @@ def run(file_list=None, format=True, lint=True, js=True, py=True, yarn=True, tes
if lint:
if py:
- results.append(py_lint(file_list))
+ results.append(py_lint(file_list, parseable=parseable))
if js:
- results.append(js_lint(file_list))
+ results.append(js_lint(file_list, parseable=parseable, format=format))
if test:
if js:
diff --git a/src/sentry/locale/ach/LC_MESSAGES/django.mo b/src/sentry/locale/ach/LC_MESSAGES/django.mo
deleted file mode 100644
index f355020b22748d..00000000000000
Binary files a/src/sentry/locale/ach/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/src/sentry/locale/ach/LC_MESSAGES/django.po b/src/sentry/locale/ach/LC_MESSAGES/django.po
deleted file mode 100644
index f380f6e5fae5db..00000000000000
--- a/src/sentry/locale/ach/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,5109 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) 2015 THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-#
-# Translators:
-msgid ""
-msgstr ""
-"Project-Id-Version: sentry\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2016-12-13 23:17+0000\n"
-"PO-Revision-Date: 2016-12-13 23:18+0000\n"
-"Last-Translator: mattrobenolt \n"
-"Language-Team: Acoli (http://www.transifex.com/getsentry/sentry/language/ach/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.3.4\n"
-"Language: ach\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#: admin.py:165
-msgid "Personal info"
-msgstr ""
-
-#: admin.py:166
-msgid "Permissions"
-msgstr ""
-
-#: admin.py:167
-msgid "Important dates"
-msgstr ""
-
-#: admin.py:255
-msgid "Password changed successfully."
-msgstr ""
-
-#: admin.py:265
-#, python-format
-msgid "Change password: %s"
-msgstr ""
-
-#: constants.py:38 static/sentry/app/views/stream/sortOptions.jsx:47
-msgid "Priority"
-msgstr ""
-
-#: constants.py:39 constants.py:46
-#: static/sentry/app/views/stream/sortOptions.jsx:52
-msgid "Last Seen"
-msgstr ""
-
-#: constants.py:40 constants.py:47
-#: static/sentry/app/views/stream/sortOptions.jsx:45
-msgid "First Seen"
-msgstr ""
-
-#: constants.py:41 static/sentry/app/views/stream/sortOptions.jsx:49
-msgid "Frequency"
-msgstr ""
-
-#: constants.py:45
-msgid "Score"
-msgstr ""
-
-#: constants.py:199
-#, python-brace-format
-msgid "The {name} integration has been enabled."
-msgstr ""
-
-#: constants.py:201
-#, python-brace-format
-msgid "The {name} integration has been disabled."
-msgstr ""
-
-#: constants.py:203
-#, python-brace-format
-msgid "Configuration for the {name} integration has been saved."
-msgstr ""
-
-#: auth/helper.py:30
-msgid "You have successfully linked your account to your SSO provider."
-msgstr ""
-
-#: auth/helper.py:32
-msgid ""
-"SSO has been configured for your organization and any existing members have "
-"been sent an email to link their accounts."
-msgstr ""
-
-#: auth/helper.py:34
-msgid "There was an error encountered during authentication."
-msgstr ""
-
-#: auth/helper.py:36
-msgid "You must be authenticated to link accounts."
-msgstr ""
-
-#: auth/password_validation.py:91
-#, python-format
-msgid ""
-"This password is too short. It must contain at least %(min_length)d "
-"character."
-msgid_plural ""
-"This password is too short. It must contain at least %(min_length)d "
-"characters."
-msgstr[0] ""
-msgstr[1] ""
-
-#: auth/password_validation.py:101
-#, python-format
-msgid "Your password must contain at least %(min_length)d character."
-msgid_plural "Your password must contain at least %(min_length)d characters."
-msgstr[0] ""
-msgstr[1] ""
-
-#: auth/password_validation.py:114
-msgid "This password is entirely numeric."
-msgstr ""
-
-#: auth/password_validation.py:119
-msgid "Your password can't be entirely numeric."
-msgstr ""
-
-#: conf/server.py:96
-msgid "Afrikaans"
-msgstr ""
-
-#: conf/server.py:97
-msgid "Arabic"
-msgstr ""
-
-#: conf/server.py:98
-msgid "Azerbaijani"
-msgstr ""
-
-#: conf/server.py:99
-msgid "Bulgarian"
-msgstr ""
-
-#: conf/server.py:100
-msgid "Belarusian"
-msgstr ""
-
-#: conf/server.py:101
-msgid "Bengali"
-msgstr ""
-
-#: conf/server.py:102
-msgid "Breton"
-msgstr ""
-
-#: conf/server.py:103
-msgid "Bosnian"
-msgstr ""
-
-#: conf/server.py:104
-msgid "Catalan"
-msgstr ""
-
-#: conf/server.py:105
-msgid "Czech"
-msgstr ""
-
-#: conf/server.py:106
-msgid "Welsh"
-msgstr ""
-
-#: conf/server.py:107
-msgid "Danish"
-msgstr ""
-
-#: conf/server.py:108
-msgid "German"
-msgstr ""
-
-#: conf/server.py:109
-msgid "Greek"
-msgstr ""
-
-#: conf/server.py:110
-msgid "English"
-msgstr ""
-
-#: conf/server.py:111
-msgid "Esperanto"
-msgstr ""
-
-#: conf/server.py:112
-msgid "Spanish"
-msgstr ""
-
-#: conf/server.py:113
-msgid "Estonian"
-msgstr ""
-
-#: conf/server.py:114
-msgid "Basque"
-msgstr ""
-
-#: conf/server.py:115
-msgid "Persian"
-msgstr ""
-
-#: conf/server.py:116
-msgid "Finnish"
-msgstr ""
-
-#: conf/server.py:117
-msgid "French"
-msgstr ""
-
-#: conf/server.py:118
-msgid "Irish"
-msgstr ""
-
-#: conf/server.py:119
-msgid "Galician"
-msgstr ""
-
-#: conf/server.py:120
-msgid "Hebrew"
-msgstr ""
-
-#: conf/server.py:121
-msgid "Hindi"
-msgstr ""
-
-#: conf/server.py:122
-msgid "Croatian"
-msgstr ""
-
-#: conf/server.py:123
-msgid "Hungarian"
-msgstr ""
-
-#: conf/server.py:124
-msgid "Interlingua"
-msgstr ""
-
-#: conf/server.py:125
-msgid "Indonesian"
-msgstr ""
-
-#: conf/server.py:126
-msgid "Icelandic"
-msgstr ""
-
-#: conf/server.py:127
-msgid "Italian"
-msgstr ""
-
-#: conf/server.py:128
-msgid "Japanese"
-msgstr ""
-
-#: conf/server.py:129
-msgid "Georgian"
-msgstr ""
-
-#: conf/server.py:130
-msgid "Kazakh"
-msgstr ""
-
-#: conf/server.py:131
-msgid "Khmer"
-msgstr ""
-
-#: conf/server.py:132
-msgid "Kannada"
-msgstr ""
-
-#: conf/server.py:133
-msgid "Korean"
-msgstr ""
-
-#: conf/server.py:134
-msgid "Luxembourgish"
-msgstr ""
-
-#: conf/server.py:135
-msgid "Lithuanian"
-msgstr ""
-
-#: conf/server.py:136
-msgid "Latvian"
-msgstr ""
-
-#: conf/server.py:137
-msgid "Macedonian"
-msgstr ""
-
-#: conf/server.py:138
-msgid "Malayalam"
-msgstr ""
-
-#: conf/server.py:139
-msgid "Mongolian"
-msgstr ""
-
-#: conf/server.py:140
-msgid "Burmese"
-msgstr ""
-
-#: conf/server.py:141
-msgid "Norwegian Bokmal"
-msgstr ""
-
-#: conf/server.py:142
-msgid "Nepali"
-msgstr ""
-
-#: conf/server.py:143
-msgid "Dutch"
-msgstr ""
-
-#: conf/server.py:144
-msgid "Norwegian Nynorsk"
-msgstr ""
-
-#: conf/server.py:145
-msgid "Ossetic"
-msgstr ""
-
-#: conf/server.py:146
-msgid "Punjabi"
-msgstr ""
-
-#: conf/server.py:147
-msgid "Polish"
-msgstr ""
-
-#: conf/server.py:148
-msgid "Portuguese"
-msgstr ""
-
-#: conf/server.py:149
-msgid "Brazilian Portuguese"
-msgstr ""
-
-#: conf/server.py:150
-msgid "Romanian"
-msgstr ""
-
-#: conf/server.py:151
-msgid "Russian"
-msgstr ""
-
-#: conf/server.py:152
-msgid "Slovak"
-msgstr ""
-
-#: conf/server.py:153
-msgid "Slovenian"
-msgstr ""
-
-#: conf/server.py:154
-msgid "Albanian"
-msgstr ""
-
-#: conf/server.py:155
-msgid "Serbian"
-msgstr ""
-
-#: conf/server.py:156
-msgid "Swedish"
-msgstr ""
-
-#: conf/server.py:157
-msgid "Swahili"
-msgstr ""
-
-#: conf/server.py:158
-msgid "Tamil"
-msgstr ""
-
-#: conf/server.py:159
-msgid "Telugu"
-msgstr ""
-
-#: conf/server.py:160
-msgid "Thai"
-msgstr ""
-
-#: conf/server.py:161
-msgid "Turkish"
-msgstr ""
-
-#: conf/server.py:162
-msgid "Tatar"
-msgstr ""
-
-#: conf/server.py:163
-msgid "Udmurt"
-msgstr ""
-
-#: conf/server.py:164
-msgid "Ukrainian"
-msgstr ""
-
-#: conf/server.py:165
-msgid "Urdu"
-msgstr ""
-
-#: conf/server.py:166
-msgid "Vietnamese"
-msgstr ""
-
-#: conf/server.py:167
-msgid "Simplified Chinese"
-msgstr ""
-
-#: conf/server.py:168
-msgid "Traditional Chinese"
-msgstr ""
-
-#: db/models/fields/bounded.py:54 db/models/fields/bounded.py:68
-msgid "Big Integer"
-msgstr ""
-
-#: debug/panels/base.py:41
-#, python-format
-msgid "%(calls)d call in %(duration).2fms"
-msgid_plural "%(calls)d calls in %(duration).2fms"
-msgstr[0] ""
-msgstr[1] ""
-
-#: debug/panels/redis.py:79
-msgid "Redis"
-msgstr ""
-
-#: interfaces/http.py:214
-msgid "Request"
-msgstr ""
-
-#: interfaces/stacktrace.py:764
-msgid "Stacktrace (most recent call first):"
-msgstr ""
-
-#: interfaces/stacktrace.py:766
-msgid "Stacktrace (most recent call last):"
-msgstr ""
-
-#: models/apikey.py:55 models/project.py:85 models/projectkey.py:54
-#: models/team.py:107
-msgid "Active"
-msgstr ""
-
-#: models/apikey.py:56 models/projectkey.py:55
-msgid "Inactive"
-msgstr ""
-
-#: models/authenticator.py:170
-msgid "Enroll"
-msgstr ""
-
-#: models/authenticator.py:171
-msgid "Info"
-msgstr ""
-
-#: models/authenticator.py:172
-#: static/sentry/app/components/activity/note.jsx:36
-#: templates/sentry/organization-members.html:127
-#: templates/sentry/organization-members.html:132
-msgid "Remove"
-msgstr ""
-
-#: models/authenticator.py:272
-msgid "Recovery Codes"
-msgstr ""
-
-#: models/authenticator.py:273
-msgid ""
-"Recovery codes can be used to access your account in the event you lose "
-"access to your device and cannot receive two-factor authentication codes."
-msgstr ""
-
-#: models/authenticator.py:276
-msgid "Activate"
-msgstr ""
-
-#: models/authenticator.py:277
-msgid "View Codes"
-msgstr ""
-
-#: models/authenticator.py:392
-msgid "Authenticator App"
-msgstr ""
-
-#: models/authenticator.py:393
-msgid ""
-"An authenticator application that supports TOTP (like Google Authenticator "
-"or 1Password) can be used to conveniently secure your account. A new token "
-"is generated every 30 seconds."
-msgstr ""
-
-#: models/authenticator.py:408
-msgid "Text Message"
-msgstr ""
-
-#: models/authenticator.py:409
-msgid ""
-"This authenticator sends you text messages for verification. It's useful as"
-" a backup method or when you do not have a phone that supports an "
-"authenticator application."
-msgstr ""
-
-#: models/authenticator.py:440
-#, python-format
-msgid ""
-"A confirmation code was sent to your phone. It is valid for %d seconds."
-msgstr ""
-
-#: models/authenticator.py:443
-msgid ""
-"Error: we failed to send a text message to you. You can try again later or "
-"sign in with a different method."
-msgstr ""
-
-#: models/authenticator.py:451
-#, python-format
-msgid ""
-"%(code)s is your Sentry two-factor enrollment code. You are about to set up "
-"text message based two-factor authentication."
-msgstr ""
-
-#: models/authenticator.py:455
-#, python-format
-msgid "%(code)s is your Sentry authentication code."
-msgstr ""
-
-#: models/authenticator.py:458
-#, python-format
-msgid "Requested from %(ip)s"
-msgstr ""
-
-#: models/authenticator.py:468
-msgid "Configure"
-msgstr ""
-
-#: models/authenticator.py:469
-msgid "U2F (Universal 2nd Factor)"
-msgstr ""
-
-#: models/authenticator.py:470
-msgid ""
-"Authenticate with a U2F hardware device. This is a device like a Yubikey or "
-"something similar which supports FIDO's U2F specification. This also "
-"requires a browser which supports this system (like Google Chrome)."
-msgstr ""
-
-#: models/authenticator.py:560
-msgid "created at"
-msgstr ""
-
-#: models/authenticator.py:561
-msgid "last used at"
-msgstr ""
-
-#: models/authenticator.py:573
-msgid "authenticator"
-msgstr ""
-
-#: models/authenticator.py:574
-msgid "authenticators"
-msgstr ""
-
-#: models/event.py:53
-msgid "message"
-msgstr ""
-
-#: models/event.py:54
-msgid "messages"
-msgstr ""
-
-#: models/event.py:123 models/group.py:415
-msgid "error"
-msgstr ""
-
-#: models/group.py:168
-#: static/sentry/app/components/organizationIssueList.jsx:49
-msgid "Unresolved"
-msgstr ""
-
-#: models/group.py:169 models/groupresolution.py:37
-msgid "Resolved"
-msgstr ""
-
-#: models/group.py:170
-msgid "Ignored"
-msgstr ""
-
-#: models/group.py:192
-msgid "grouped messages"
-msgstr ""
-
-#: models/group.py:193
-msgid "grouped message"
-msgstr ""
-
-#: models/groupresolution.py:36
-msgid "Pending"
-msgstr ""
-
-#: models/organization.py:80 models/tagkey.py:67
-msgid "Visible"
-msgstr ""
-
-#: models/organization.py:81 models/project.py:86 models/tagkey.py:68
-#: models/team.py:108
-msgid "Pending Deletion"
-msgstr ""
-
-#: models/organization.py:82 models/project.py:87 models/tagkey.py:69
-#: models/team.py:109
-msgid "Deletion in Progress"
-msgstr ""
-
-#: models/user.py:33
-msgid "username"
-msgstr ""
-
-#: models/user.py:36
-msgid "name"
-msgstr ""
-
-#: models/user.py:38 models/useremail.py:22
-msgid "email address"
-msgstr ""
-
-#: models/user.py:40
-msgid "staff status"
-msgstr ""
-
-#: models/user.py:41
-msgid "Designates whether the user can log into this admin site."
-msgstr ""
-
-#: models/user.py:44
-msgid "active"
-msgstr ""
-
-#: models/user.py:45
-msgid ""
-"Designates whether this user should be treated as active. Unselect this "
-"instead of deleting accounts."
-msgstr ""
-
-#: models/user.py:48
-msgid "superuser status"
-msgstr ""
-
-#: models/user.py:49
-msgid ""
-"Designates that this user has all permissions without explicitly assigning "
-"them."
-msgstr ""
-
-#: models/user.py:52
-msgid "managed"
-msgstr ""
-
-#: models/user.py:53
-msgid ""
-"Designates whether this user should be treated as managed. Select this to "
-"disallow the user from modifying their account (username, password, etc)."
-msgstr ""
-
-#: models/user.py:57
-msgid "password expired"
-msgstr ""
-
-#: models/user.py:58
-msgid ""
-"If set to true then the user needs to change the password on next sign in."
-msgstr ""
-
-#: models/user.py:61
-msgid "date of last password change"
-msgstr ""
-
-#: models/user.py:62
-msgid "The date the password was changed last."
-msgstr ""
-
-#: models/user.py:66
-msgid "date joined"
-msgstr ""
-
-#: models/user.py:76
-msgid "user"
-msgstr ""
-
-#: models/user.py:77 static/sentry/app/components/groupListHeader.jsx:16
-msgid "users"
-msgstr ""
-
-#: models/useremail.py:27
-msgid "verified"
-msgstr ""
-
-#: models/useremail.py:28
-msgid "Designates whether this user has confirmed their email."
-msgstr ""
-
-#: plugins/base/configuration.py:65 web/frontend/project_plugins.py:24
-#: web/frontend/project_quotas.py:35
-msgid "Your settings were saved successfully."
-msgstr ""
-
-#: plugins/sentry_webhooks/plugin.py:33
-msgid "Callback URLs"
-msgstr ""
-
-#: plugins/sentry_webhooks/plugin.py:36
-msgid "Enter callback URLs to POST new events to (one per line)."
-msgstr ""
-
-#: templates/sentry/403-csrf-failure.html:5
-#: templates/sentry/403-csrf-failure.html:10
-msgid "CSRF Verification Failed"
-msgstr ""
-
-#: templates/sentry/403-csrf-failure.html:13
-msgid "A required security token was not found or was invalid."
-msgstr ""
-
-#: templates/sentry/403-csrf-failure.html:15
-msgid "If you're continually seeing this issue, try the following:"
-msgstr ""
-
-#: templates/sentry/403-csrf-failure.html:18
-msgid "Clear cookies (at least for Sentry's domain)."
-msgstr ""
-
-#: templates/sentry/403-csrf-failure.html:19
-msgid "Reload the page you're trying to submit (don't re-submit data)."
-msgstr ""
-
-#: templates/sentry/403-csrf-failure.html:20
-msgid "Re-enter the information, and submit the form again."
-msgstr ""
-
-#: static/sentry/app/components/errors/notFound.jsx:10
-#: templates/sentry/404.html:5
-msgid "Page Not Found"
-msgstr ""
-
-#: templates/sentry/404.html:14
-msgid "The page you are looking for was not found."
-msgstr ""
-
-#: templates/sentry/404.html:15
-msgid "You may wish to try the following:"
-msgstr ""
-
-#: templates/sentry/404.html:21
-msgid "Return to the dashboard"
-msgstr ""
-
-#: templates/sentry/500.html:5
-msgid "Internal Server Error"
-msgstr ""
-
-#: templates/sentry/accept-organization-invite.html:5
-#: templates/sentry/accept-organization-invite.html:15
-msgid "Organization Invite"
-msgstr ""
-
-#: templates/sentry/accept-organization-invite.html:19
-#, python-format
-msgid "%(org_name)s is using Sentry to aggregate errors."
-msgstr ""
-
-#: templates/sentry/accept-organization-invite.html:30
-#, python-format
-msgid ""
-"You have been invited to join this organization, which manages "
-"%(project_count)s project(s), including:"
-msgstr ""
-
-#: templates/sentry/accept-organization-invite.html:40
-msgid ""
-"To continue, you must either login to your existing account, or create a new"
-" one."
-msgstr ""
-
-#: templates/sentry/accept-organization-invite.html:44
-msgid "Login as an existing user"
-msgstr ""
-
-#: templates/sentry/accept-organization-invite.html:46
-msgid "Create a new account"
-msgstr ""
-
-#: templates/sentry/accept-organization-invite.html:53
-#, python-format
-msgid "Join the %(org_name)s organization"
-msgstr ""
-
-#: templates/sentry/admin-queue.html:8 templates/sentry/bases/admin.html:14
-msgid "Queue"
-msgstr ""
-
-#: templates/sentry/auth-confirm-identity.html:7
-#: templates/sentry/auth-confirm-link.html:7
-msgid "Confirm Identity"
-msgstr ""
-
-#: templates/sentry/auth-confirm-identity.html:43
-#: templates/sentry/auth-link-login.html:7
-#: templates/sentry/auth-link-login.html:31 templates/sentry/login.html:7
-#: templates/sentry/login.html:38 templates/sentry/login.html.py:12
-#: templates/sentry/organization-login.html:7
-#: templates/sentry/organization-login.html:32
-#: templates/sentry/organization-login.html:55
-msgid "Login"
-msgstr ""
-
-#: templates/sentry/auth-confirm-identity.html:44
-#: templates/sentry/auth-link-login.html:31 templates/sentry/login.html:38
-#: templates/sentry/organization-login.html:55
-msgid "Lost your password?"
-msgstr ""
-
-#: templates/sentry/auth-link-identity.html:7
-msgid "Link Identity"
-msgstr ""
-
-#: templates/sentry/create-organization-member.html:6
-#: templates/sentry/create-organization-member.html:18
-msgid "Add Member to Organization"
-msgstr ""
-
-#: templates/sentry/create-organization-member.html:23
-msgid ""
-"Invite a member to join this organization via their email address. If they "
-"do not already have an account, they will first be asked to create one."
-msgstr ""
-
-#: templates/sentry/create-organization-member.html:25
-msgid "You may add a user by their username if they already have an account."
-msgstr ""
-
-#: templates/sentry/create-organization-member.html:43
-msgid "Add Member"
-msgstr ""
-
-#: static/sentry/app/components/sidebar/organizationSelector.jsx:84
-#: templates/sentry/create-organization.html:6
-msgid "New Organization"
-msgstr ""
-
-#: templates/sentry/create-organization.html:11
-msgid "Create a New Organization"
-msgstr ""
-
-#: templates/sentry/create-organization.html:13
-msgid ""
-"Organizations represent the top level in your hierarchy. You'll be able to "
-"bundle a collection of teams within an organization as well as give "
-"organization-wide permissions to users."
-msgstr ""
-
-#: templates/sentry/create-organization.html:24
-msgid "Create Organization"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeContainer.jsx:37
-#: templates/sentry/create-project.html:8
-#: templates/sentry/projects/cannot_create_teams.html:5
-msgid "New Project"
-msgstr ""
-
-#: templates/sentry/create-project.html:13
-msgid "Create a New Project"
-msgstr ""
-
-#: templates/sentry/create-project.html:15
-msgid ""
-"Projects allow you to scope events to a specific application in your "
-"organization. For example, you might have separate projects for production "
-"vs development instances, or separate projects for your web app and mobile "
-"app."
-msgstr ""
-
-#: templates/sentry/create-project.html:20
-#: templates/sentry/organization-api-key-settings.html:19
-#: templates/sentry/organization-member-settings.html:22
-#: templates/sentry/organization-settings.html:17
-#: templates/sentry/partial/form_base.html:5
-msgid "Please correct the errors below."
-msgstr ""
-
-#: templates/sentry/create-project.html:31
-msgid "Create Project"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeContainer.jsx:47
-#: templates/sentry/create-team.html:7
-msgid "New Team"
-msgstr ""
-
-#: templates/sentry/create-team.html:11
-msgid "Create a New Team"
-msgstr ""
-
-#: templates/sentry/create-team.html:13
-msgid ""
-"Teams group members' access to a specific focus, e.g. a major product or "
-"application that may have sub-projects."
-msgstr ""
-
-#: templates/sentry/error-page-embed.html:258
-msgid "It looks like we're having some internal issues."
-msgstr ""
-
-#: templates/sentry/error-page-embed.html:259
-msgid "Our team has been notified."
-msgstr ""
-
-#: templates/sentry/error-page-embed.html:259
-msgid "If you'd like to help, tell us what happened below."
-msgstr ""
-
-#: static/sentry/app/views/stream/savedSearchSelector.jsx:121
-#: templates/sentry/error-page-embed.html:265 web/forms/__init__.py:19
-#: web/forms/accounts.py:273 web/forms/add_project.py:15
-#: web/forms/add_team.py:13
-msgid "Name"
-msgstr ""
-
-#: templates/sentry/error-page-embed.html:269
-#: templates/sentry/organization-member-details.html:32
-#: templates/sentry/organization-member-settings.html:36
-#: templates/sentry/projects/manage.html:51 web/forms/accounts.py:161
-#: web/forms/accounts.py:275 web/forms/accounts.py:483
-msgid "Email"
-msgstr ""
-
-#: templates/sentry/error-page-embed.html:273
-msgid "What happened?"
-msgstr ""
-
-#: templates/sentry/error-page-embed.html:278
-msgid "Submit Crash Report"
-msgstr ""
-
-#: static/sentry/app/components/alertMessage.jsx:35
-#: templates/sentry/error-page-embed.html:279
-msgid "Close"
-msgstr ""
-
-#: templates/sentry/error-page-embed.html:282
-msgid "Crash reports powered by enabled."
-msgstr ""
-
-#: templates/sentry/account/security.html:18
-#: templates/sentry/projects/keys.html:35
-msgid "Enable"
-msgstr ""
-
-#: templates/sentry/account/security.html:19
-#: templates/sentry/account/twofactor.html:13
-msgid "Two-factor authentication is currently disabled."
-msgstr ""
-
-#: templates/sentry/account/settings.html:14
-msgid "Your email address has not been verified. "
-msgstr ""
-
-#: templates/sentry/account/settings.html:18
-msgid "Resend Verification Email."
-msgstr ""
-
-#: templates/sentry/account/settings.html:59
-msgid "Optional"
-msgstr ""
-
-#: templates/sentry/account/settings.html:68
-msgid "Verification"
-msgstr ""
-
-#: templates/sentry/account/sudo.html:7
-msgid "Confirm Password"
-msgstr ""
-
-#: templates/sentry/account/sudo.html:33
-#: templates/sentry/bases/account.html:20
-msgid "Back to organization"
-msgstr ""
-
-#: templates/sentry/account/sudo.html:38
-msgid "Help us keep your account safe by confirming your password."
-msgstr ""
-
-#: templates/sentry/account/sudo.html:44
-msgid "Your password was not valid."
-msgstr ""
-
-#: templates/sentry/account/twofactor.html:8
-msgid "Two-factor Authentication"
-msgstr ""
-
-#: templates/sentry/account/twofactor.html:55
-#: templates/sentry/account/twofactor/configure.html:41
-msgid "Back"
-msgstr ""
-
-#: templates/sentry/account/recover/confirm.html:6
-#: templates/sentry/account/recover/confirm.html:9
-#: templates/sentry/account/recover/failure.html:6
-#: templates/sentry/account/recover/failure.html:9
-#: templates/sentry/account/recover/index.html:6
-#: templates/sentry/account/recover/index.html:9
-#: templates/sentry/account/recover/sent.html:6
-#: templates/sentry/account/recover/sent.html:9
-msgid "Recover Account"
-msgstr ""
-
-#: templates/sentry/account/recover/confirm.html:11
-msgid "You have confirmed your email, and may now update your password below."
-msgstr ""
-
-#: templates/sentry/account/recover/confirm.html:21
-msgid "Change Password"
-msgstr ""
-
-#: templates/sentry/account/recover/expired.html:6
-#: templates/sentry/account/recover/expired.html:10
-msgid "Password Expired"
-msgstr ""
-
-#: templates/sentry/account/recover/expired.html:11
-msgid "The password on your account expired."
-msgstr ""
-
-#: templates/sentry/account/recover/expired.html:13
-#: templates/sentry/account/recover/sent.html:11
-msgid ""
-"We have sent an email to the address registered with this account containing"
-" further instructions to reset your password."
-msgstr ""
-
-#: templates/sentry/account/recover/failure.html:11
-#, python-format
-msgid ""
-"We were unable to confirm your identity. Either the link you followed is "
-"invalid, or it has expired. You can always try "
-"again."
-msgstr ""
-
-#: templates/sentry/account/recover/index.html:10
-msgid "We will send a confirmation email to this address:"
-msgstr ""
-
-#: templates/sentry/account/recover/index.html:22
-msgid "Send Email"
-msgstr ""
-
-#: templates/sentry/account/twofactor/configure.html:22
-#: templates/sentry/account/twofactor/configure.html:24
-#: templatetags/sentry_helpers.py:190
-msgid "never"
-msgstr ""
-
-#: templates/sentry/account/twofactor/configure_u2f.html:33
-msgid "Add Another Device"
-msgstr ""
-
-#: templates/sentry/account/twofactor/enroll_sms.html:7
-#: templates/sentry/account/twofactor/enroll_totp.html:7
-#: templates/sentry/account/twofactor/enroll_u2f.html:7
-msgid "Enrollment: "
-msgstr ""
-
-#: templates/sentry/account/twofactor/enroll_sms.html:39
-msgid "Send Confirmation Code"
-msgstr ""
-
-#: static/sentry/app/components/linkWithConfirmation.jsx:55
-#: templates/sentry/account/twofactor/enroll_sms.html:41
-#: templates/sentry/account/twofactor/enroll_totp.html:42
-#: templates/sentry/admin/users/remove.html:19
-msgid "Confirm"
-msgstr ""
-
-#: templates/sentry/account/twofactor/remove.html:7
-msgid "Remove Method:"
-msgstr ""
-
-#: templates/sentry/account/twofactor/remove.html:31
-#: templates/sentry/admin/status/mail.html:37
-msgid "Yes"
-msgstr ""
-
-#: templates/sentry/account/twofactor/remove.html:32
-#: templates/sentry/admin/status/mail.html:37
-msgid "No"
-msgstr ""
-
-#: templates/sentry/admin/status/env.html:9
-msgid "Server Status"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/richHttpContent.jsx:99
-#: templates/sentry/admin/status/env.html:12
-#: templates/sentry/bases/admin.html:15
-msgid "Environment"
-msgstr ""
-
-#: templates/sentry/admin/status/env.html:17
-msgid "Server Version"
-msgstr ""
-
-#: templates/sentry/admin/status/env.html:22
-msgid "Python Version"
-msgstr ""
-
-#: templates/sentry/admin/status/env.html:26
-msgid "Configuration File"
-msgstr ""
-
-#: templates/sentry/admin/status/env.html:30
-msgid "Uptime"
-msgstr ""
-
-#: templates/sentry/admin/status/env.html:37
-msgid "Environment not found (are you using the builtin Sentry webserver?)."
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:85
-#: templates/sentry/admin/status/env.html:40
-#: templates/sentry/projects/manage.html:181
-msgid "Configuration"
-msgstr ""
-
-#: templates/sentry/admin/status/mail.html:9
-msgid "Mail Configuration"
-msgstr ""
-
-#: templates/sentry/admin/status/mail.html:12
-msgid "SMTP Settings"
-msgstr ""
-
-#: templates/sentry/admin/status/mail.html:15
-msgid "From Address"
-msgstr ""
-
-#: templates/sentry/admin/status/mail.html:20
-msgid "Host"
-msgstr ""
-
-#: templates/sentry/admin/status/mail.html:25 web/forms/accounts.py:274
-msgid "Username"
-msgstr ""
-
-#: templates/sentry/admin/status/mail.html:27
-#: templates/sentry/admin/status/mail.html:32
-msgid "not set"
-msgstr ""
-
-#: templates/sentry/admin/status/mail.html:30 web/forms/accounts.py:53
-msgid "Password"
-msgstr ""
-
-#: templates/sentry/admin/status/mail.html:35
-msgid "TLS?"
-msgstr ""
-
-#: templates/sentry/admin/status/mail.html:40
-msgid "Mailing List Namespace"
-msgstr ""
-
-#: templates/sentry/admin/status/mail.html:46
-msgid "Test Settings"
-msgstr ""
-
-#: templates/sentry/admin/status/mail.html:51
-msgid ""
-"Send an email to your account's email address to confirm that everything is "
-"configured correctly."
-msgstr ""
-
-#: templates/sentry/admin/status/mail.html:59
-#, python-format
-msgid "Send a test email to %(email)s"
-msgstr ""
-
-#: static/sentry/app/components/events/packageData.jsx:28
-#: templates/sentry/admin/status/packages.html:8
-#: templates/sentry/bases/admin.html:16
-msgid "Packages"
-msgstr ""
-
-#: templates/sentry/admin/status/packages.html:11
-msgid "Extensions"
-msgstr ""
-
-#: templates/sentry/admin/status/packages.html:23
-msgid "No extensions registered."
-msgstr ""
-
-#: templates/sentry/admin/status/packages.html:26
-msgid "Modules"
-msgstr ""
-
-#: templates/sentry/admin/users/edit.html:8
-msgid "Change User"
-msgstr ""
-
-#: templates/sentry/admin/users/edit.html:22
-#: templates/sentry/admin/users/remove.html:6
-msgid "Remove User"
-msgstr ""
-
-#: templates/sentry/admin/users/edit.html:24
-msgid "Cannot remove yourself"
-msgstr ""
-
-#: static/sentry/app/views/adminProjects.jsx:36
-#: templates/sentry/admin/users/edit.html:30
-#: templates/sentry/bases/admin.html:25
-msgid "Projects"
-msgstr ""
-
-#: templates/sentry/admin/users/edit.html:40
-msgid "Daily Events"
-msgstr ""
-
-#: templates/sentry/admin/users/new.html:6
-msgid "New User"
-msgstr ""
-
-#: static/sentry/app/components/sidebar/organizationSelector.jsx:72
-#: templates/sentry/bases/account.html:7 templates/sentry/bases/admin.html:18
-#: templates/sentry/bases/organization.html:82
-msgid "Settings"
-msgstr ""
-
-#: static/sentry/app/components/sidebar/userNav.jsx:44
-#: templates/sentry/bases/account.html:16 templates/sentry/bases/modal.html:18
-msgid "Sign out"
-msgstr ""
-
-#: static/sentry/app/components/sidebar/userNav.jsx:39
-#: templates/sentry/bases/account.html:35 web/forms/accounts.py:48
-msgid "Account"
-msgstr ""
-
-#: templates/sentry/bases/account.html:36
-msgid "Avatar"
-msgstr ""
-
-#: static/sentry/app/components/group/sidebar.jsx:133
-#: templates/sentry/bases/account.html:38
-msgid "Notifications"
-msgstr ""
-
-#: templates/sentry/bases/account.html:39
-msgid "Emails"
-msgstr ""
-
-#: templates/sentry/bases/account.html:43
-msgid "Security"
-msgstr ""
-
-#: static/sentry/app/components/sidebar/userNav.jsx:42
-#: templates/sentry/bases/admin.html:7 web/forms/__init__.py:32
-msgid "Admin"
-msgstr ""
-
-#: templates/sentry/bases/admin.html:10
-msgid "System"
-msgstr ""
-
-#: static/sentry/app/components/projectHeader/index.jsx:46
-#: templates/sentry/bases/admin.html:12
-msgid "Overview"
-msgstr ""
-
-#: templates/sentry/bases/admin.html:13
-msgid "Buffer"
-msgstr ""
-
-#: templates/sentry/bases/admin.html:17
-msgid "Mail"
-msgstr ""
-
-#: static/sentry/app/components/sidebar/organizationSelector.jsx:62
-#: templates/sentry/bases/admin.html:24
-msgid "Organizations"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:389
-#: templates/sentry/bases/admin.html:26
-msgid "Users"
-msgstr ""
-
-#: templates/sentry/bases/admin.html:31
-msgid "Plugins"
-msgstr ""
-
-#: templates/sentry/bases/organization.html:8
-msgid "Organization Settings"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeSidebar.jsx:40
-#: templates/sentry/bases/organization.html:20
-msgid "Dashboard"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeSidebar.jsx:44
-#: templates/sentry/bases/organization.html:25
-msgid "Projects & Teams"
-msgstr ""
-
-#: static/sentry/app/views/organizationStats/index.jsx:243
-#: templates/sentry/bases/organization.html:31
-msgid "Stats"
-msgstr ""
-
-#: static/sentry/app/components/projectHeader/index.jsx:34
-#: templates/sentry/bases/organization.html:36
-msgid "Issues"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeSidebar.jsx:52
-#: templates/sentry/bases/organization.html:38
-msgid "Assigned to Me"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeSidebar.jsx:53
-#: templates/sentry/bases/organization.html:39
-msgid "Bookmarks"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeSidebar.jsx:54
-#: templates/sentry/bases/organization.html:40
-msgid "History"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeSidebar.jsx:78
-#: templates/sentry/bases/organization.html:75
-msgid "Audit Log"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:91
-#: templates/sentry/bases/organization.html:76
-#: templates/sentry/projects/manage.html:191
-#: templates/sentry/projects/quotas.html:7
-#: templates/sentry/projects/quotas.html:10
-msgid "Rate Limits"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeSidebar.jsx:84
-#: templates/sentry/bases/organization.html:78
-msgid "Repositories"
-msgstr ""
-
-#: templates/sentry/bases/twofactor_settings.html:11
-msgid "Method:"
-msgstr ""
-
-#: templates/sentry/groups/public_details.html:31
-msgid "Newer Event"
-msgstr ""
-
-#: templates/sentry/groups/public_details.html:32
-msgid "Older Event"
-msgstr ""
-
-#: templates/sentry/groups/public_details.html:37
-msgid ""
-"You are viewing a publicly available version of this event's data. Some "
-"information may not be available."
-msgstr ""
-
-#: templates/sentry/groups/public_details.html:43
-msgid "Full message"
-msgstr ""
-
-#: templates/sentry/partial/_form.html:14
-msgid "Test Configuration"
-msgstr ""
-
-#: static/sentry/app/components/pagination.jsx:44
-#: templates/sentry/partial/_pager.html:5
-#: templates/sentry/users/details.html:42
-#: templates/sentry/users/details.html:59
-msgid "Previous"
-msgstr ""
-
-#: static/sentry/app/components/pagination.jsx:52
-#: templates/sentry/partial/_pager.html:6
-#: templates/sentry/users/details.html:43
-#: templates/sentry/users/details.html:60
-msgid "Next"
-msgstr ""
-
-#: templates/sentry/partial/interfaces/http_email.html:8
-msgid "URL"
-msgstr ""
-
-#: templates/sentry/partial/interfaces/http_email.html:13
-msgid "Method"
-msgstr ""
-
-#: static/sentry/app/views/stream/savedSearchSelector.jsx:128
-#: templates/sentry/partial/interfaces/http_email.html:19
-msgid "Query"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/richHttpContent.jsx:77
-#: templates/sentry/partial/interfaces/http_email.html:27
-msgid "Fragment"
-msgstr ""
-
-#: static/sentry/app/components/events/contextSummary.jsx:82
-#: templates/sentry/partial/interfaces/user_email.html:13
-msgid "ID:"
-msgstr ""
-
-#: templates/sentry/partial/interfaces/user_email.html:19
-msgid "IP Address:"
-msgstr ""
-
-#: static/sentry/app/components/events/contextSummary.jsx:84
-#: templates/sentry/partial/interfaces/user_email.html:25
-msgid "Username:"
-msgstr ""
-
-#: templates/sentry/partial/interfaces/user_email.html:31
-msgid "Email:"
-msgstr ""
-
-#: templates/sentry/plugins/site_configuration.html:5
-msgid "Changes to your configuration were saved successfully."
-msgstr ""
-
-#: templates/sentry/plugins/bases/issue/create_issue.html:17
-msgid "Create New"
-msgstr ""
-
-#: templates/sentry/plugins/bases/issue/create_issue.html:20
-msgid "Link Existing"
-msgstr ""
-
-#: static/sentry/app/plugins/components/issueActions.jsx:179
-#: templates/sentry/plugins/bases/issue/create_issue.html:46
-msgid "Create Issue"
-msgstr ""
-
-#: static/sentry/app/plugins/components/issueActions.jsx:205
-#: templates/sentry/plugins/bases/issue/create_issue.html:64
-msgid "Link Issue"
-msgstr ""
-
-#: templates/sentry/plugins/bases/issue/needs_auth.html:13
-#, python-format
-msgid ""
-"You still need to associate an identity with %(title)s before you can\n"
-" create issues with this service."
-msgstr ""
-
-#: templates/sentry/plugins/bases/issue/not_configured.html:13
-#, python-format
-msgid ""
-"Your server administrator will need to configure authentication with\n"
-" %(auth_provider)s before you can use this plugin."
-msgstr ""
-
-#: templates/sentry/plugins/bases/issue/not_configured.html:23
-#, python-format
-msgid ""
-"You still need to configure this plugin before you "
-"can use it."
-msgstr ""
-
-#: templates/sentry/projects/cannot_create_teams.html:9
-msgid ""
-"You are not able to create a new project because you are not a member of any"
-" teams. Ask an administrator to add you to a team."
-msgstr ""
-
-#: templates/sentry/projects/edit_key.html:7
-#: templates/sentry/projects/edit_key.html:11
-msgid "Edit API Key"
-msgstr ""
-
-#: templates/sentry/projects/edit_key.html:25
-msgid "Created"
-msgstr ""
-
-#: templates/sentry/projects/edit_key.html:38
-msgid ""
-"Your credentials are bound to public and secret key (though both should be "
-"considered semi-secret). Different clients will require different "
-"credentials, so make sure you check the documentation before plugging things"
-" in."
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/overview.jsx:66
-#: templates/sentry/projects/edit_key.html:40
-#: templates/sentry/projects/keys.html:53
-msgid "DSN"
-msgstr ""
-
-#: templates/sentry/projects/edit_key.html:46
-#: templates/sentry/projects/keys.html:58
-msgid "DSN (Public)"
-msgstr ""
-
-#: templates/sentry/projects/edit_key.html:50
-#: templates/sentry/projects/keys.html:60
-#, python-format
-msgid ""
-"Use your public DSN with browser-based clients such as raven-js."
-msgstr ""
-
-#: templates/sentry/projects/edit_key.html:53
-msgid "Public Key"
-msgstr ""
-
-#: templates/sentry/projects/edit_key.html:59
-msgid "Secret Key"
-msgstr ""
-
-#: templates/sentry/projects/edit_key.html:65
-msgid "Project ID"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:110
-#: templates/sentry/projects/keys.html:6
-#: templates/sentry/projects/keys.html:15
-msgid "Client Keys"
-msgstr ""
-
-#: templates/sentry/projects/keys.html:12
-msgid "Generate New Key"
-msgstr ""
-
-#: static/sentry/app/components/pluginConfig.jsx:94
-#: templates/sentry/projects/keys.html:30
-msgid "Disable"
-msgstr ""
-
-#: templates/sentry/projects/keys.html:42
-msgid "Revoke"
-msgstr ""
-
-#: templates/sentry/projects/keys.html:65
-msgid "CSP Endpoint"
-msgstr ""
-
-#: templates/sentry/projects/keys.html:67
-msgid ""
-"Use your CSP endpoint in the report-uri directive in your Content-Security-Policy header."
-msgstr ""
-
-#: static/sentry/app/components/projectHeader/index.jsx:75
-#: templates/sentry/projects/manage.html:32
-msgid "Project Settings"
-msgstr ""
-
-#: templates/sentry/projects/manage.html:40
-msgid "Project Details"
-msgstr ""
-
-#: templates/sentry/projects/manage.html:60
-msgid "Event Settings"
-msgstr ""
-
-#: templates/sentry/projects/manage.html:76
-msgid "Client Security"
-msgstr ""
-
-#: templates/sentry/projects/manage.html:80
-#, python-format
-msgid ""
-"Configure origin URLs which Sentry should accept events from. This is used "
-"for communication with clients like raven-js."
-msgstr ""
-
-#: templates/sentry/projects/manage.html:82
-msgid ""
-"This will restrict requests based on the Origin and "
-"Referer headers."
-msgstr ""
-
-#: templates/sentry/projects/manage.html:93
-#: templates/sentry/projects/manage.html:102
-#: templates/sentry/projects/remove.html:6
-#: templates/sentry/projects/remove.html:11
-#: templates/sentry/projects/remove.html:29
-msgid "Remove Project"
-msgstr ""
-
-#: templates/sentry/projects/manage.html:97
-msgid "You do not have the required permission to remove this project."
-msgstr ""
-
-#: templates/sentry/projects/manage.html:99
-msgid ""
-"This project cannot be removed. It is used internally by the Sentry server."
-msgstr ""
-
-#: templates/sentry/projects/manage.html:168
-msgid ""
-"This option is enforced by your organization's settings and cannot be "
-"customized per-project."
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:87
-#: templates/sentry/projects/manage.html:184
-msgid "General"
-msgstr ""
-
-#: static/sentry/app/components/events/eventTags.jsx:31
-#: templates/sentry/projects/manage.html:195
-#: templates/sentry/projects/manage_tags.html:7
-#: templates/sentry/projects/manage_tags.html:10
-msgid "Tags"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:96
-#: templates/sentry/projects/manage.html:204
-msgid "Saved Searches"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:97
-#: templates/sentry/projects/manage.html:207
-msgid "Debug Symbols"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:99
-#: templates/sentry/projects/manage.html:210
-msgid "Data"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:104
-#: templates/sentry/projects/manage.html:213
-msgid "Error Tracking"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:106
-#: templates/sentry/projects/manage.html:217
-msgid "CSP Reports"
-msgstr ""
-
-#: static/sentry/app/components/projectHeader/index.jsx:51
-#: templates/sentry/projects/manage.html:221
-msgid "User Feedback"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:109
-#: templates/sentry/projects/manage.html:224
-msgid "Inbound Filters"
-msgstr ""
-
-#: templates/sentry/projects/manage.html:227
-msgid "Client Keys (DSN)"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:112
-#: templates/sentry/projects/manage.html:231
-#: templates/sentry/projects/plugins/list.html:10
-msgid "Integrations"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:114
-#: templates/sentry/projects/manage.html:234
-msgid "All Integrations"
-msgstr ""
-
-#: templates/sentry/projects/manage_tags.html:13
-#, python-format
-msgid ""
-"Each event in Sentry may be annotated with various tags (key and value "
-"pairs). Learn how to add custom tags."
-msgstr ""
-
-#: templates/sentry/projects/manage_tags.html:53
-msgid "We have not yet recorded any tags for this project."
-msgstr ""
-
-#: templates/sentry/projects/quotas.html:13
-msgid ""
-"With the nature of Sentry, sometimes the amount of data collected can be "
-"overwhelming. You can set rate limits per-project to ensure that a single "
-"flood of errors won't affect any other projects utilizing Sentry."
-msgstr ""
-
-#: templates/sentry/projects/quotas.html:15
-msgid ""
-"Rate limits apply on a per-minute basis, which means that they rollover at "
-"the start of a new minute. When you attempt to send an event and the project"
-" is over its quota, the client will receive an HTTP 429 (Too Many Requests) "
-"response."
-msgstr ""
-
-#: templates/sentry/projects/quotas.html:18
-msgid "Note: The Sentry application is not configured to manage rate limits."
-msgstr ""
-
-#: templates/sentry/projects/quotas.html:20
-#, python-format
-msgid ""
-"Your team has %(team_quota)s events per minute allocated "
-"collectively among projects."
-msgstr ""
-
-#: templates/sentry/projects/quotas.html:22
-#, python-format
-msgid ""
-"The Sentry system has %(system_quota)s events per minute "
-"allocated collectively among projects."
-msgstr ""
-
-#: templates/sentry/projects/remove.html:18
-msgid "Removing this project is permanent and cannot be undone!"
-msgstr ""
-
-#: templates/sentry/projects/remove.html:20
-msgid "This will also remove the all associated event data."
-msgstr ""
-
-#: templates/sentry/projects/plugins/configure.html:15
-msgid "Enable Plugin"
-msgstr ""
-
-#: templates/sentry/projects/plugins/configure.html:21
-msgid "Disable Plugin"
-msgstr ""
-
-#: templates/sentry/projects/plugins/configure.html:27
-msgid "Are you sure you wish to reset all configuration for this plugin?"
-msgstr ""
-
-#: templates/sentry/projects/plugins/configure.html:29
-msgid "Reset Configuration"
-msgstr ""
-
-#: templates/sentry/projects/plugins/configure.html:38
-msgid "Changes to your project were saved successfully."
-msgstr ""
-
-#: templates/sentry/projects/plugins/list.html:7
-msgid "Manage Integrations"
-msgstr ""
-
-#: templates/sentry/projects/plugins/list.html:25
-msgid "n/a"
-msgstr ""
-
-#: templates/sentry/teams/base.html:5
-msgid "Team List"
-msgstr ""
-
-#: static/sentry/app/views/teamDetails.jsx:91
-#: templates/sentry/teams/remove.html:7 templates/sentry/teams/remove.html:12
-#: templates/sentry/teams/remove.html:39
-msgid "Remove Team"
-msgstr ""
-
-#: templates/sentry/teams/remove.html:19
-msgid "Removing this team is permanent and cannot be undone!"
-msgstr ""
-
-#: templates/sentry/teams/remove.html:22
-msgid "This will also remove all associated projects and events:"
-msgstr ""
-
-#: templatetags/sentry_helpers.py:134
-msgid "b"
-msgstr ""
-
-#: templatetags/sentry_helpers.py:135
-msgid "m"
-msgstr ""
-
-#: templatetags/sentry_helpers.py:136
-msgid "k"
-msgstr ""
-
-#: templatetags/sentry_helpers.py:194
-msgid "0 minutes"
-msgstr ""
-
-#: templatetags/sentry_helpers.py:195
-msgid "just now"
-msgstr ""
-
-#: static/sentry/app/views/projectDashboard.jsx:139
-#: templatetags/sentry_helpers.py:196
-msgid "1 day"
-msgstr ""
-
-#: templatetags/sentry_helpers.py:197
-msgid "yesterday"
-msgstr ""
-
-#: templatetags/sentry_helpers.py:198
-msgid " ago"
-msgstr ""
-
-#: web/decorators.py:12
-msgid "The link you followed is invalid or expired."
-msgstr ""
-
-#: web/forms/__init__.py:24
-msgid ""
-"Send this user a welcome email which will contain their generated password."
-msgstr ""
-
-#: web/forms/__init__.py:33
-msgid "Designates whether this user can perform administrative functions."
-msgstr ""
-
-#: web/forms/__init__.py:34
-msgid "Superuser"
-msgstr ""
-
-#: web/forms/__init__.py:35
-msgid ""
-"Designates whether this user has all permissions without explicitly "
-"assigning them."
-msgstr ""
-
-#: web/forms/__init__.py:57
-msgid "Disable the account."
-msgstr ""
-
-#: web/forms/__init__.py:58
-msgid "Permanently remove the user and their data."
-msgstr ""
-
-#: web/forms/accounts.py:49
-msgid "username or email"
-msgstr ""
-
-#: web/forms/accounts.py:54
-msgid "password"
-msgstr ""
-
-#: web/forms/accounts.py:59
-#, python-format
-msgid ""
-"Please enter a correct %(username)s and password. Note that both fields may "
-"be case-sensitive."
-msgstr ""
-
-#: web/forms/accounts.py:61
-msgid ""
-"You have made too many failed authentication attempts. Please try again "
-"later."
-msgstr ""
-
-#: web/forms/accounts.py:63
-msgid ""
-"Your Web browser doesn't appear to have cookies enabled. Cookies are "
-"required for logging in."
-msgstr ""
-
-#: web/forms/accounts.py:65
-msgid "This account is inactive."
-msgstr ""
-
-#: web/forms/accounts.py:175
-msgid "An account is already registered with that email address."
-msgstr ""
-
-#: web/forms/accounts.py:193
-msgid "Username or email"
-msgstr ""
-
-#: web/forms/accounts.py:201
-msgid "We were unable to find a matching user."
-msgstr ""
-
-#: web/forms/accounts.py:205
-msgid ""
-"The account you are trying to recover is managed and does not support "
-"password recovery."
-msgstr ""
-
-#: web/forms/accounts.py:208
-msgid "Multiple accounts were found matching this email address."
-msgstr ""
-
-#: web/forms/accounts.py:222
-msgid "Primary Email"
-msgstr ""
-
-#: web/forms/accounts.py:225
-msgid "New Email"
-msgstr ""
-
-#: web/forms/accounts.py:231 web/forms/accounts.py:283
-msgid "Current password"
-msgstr ""
-
-#: web/forms/accounts.py:233
-msgid "You will need to enter your current account password to make changes."
-msgstr ""
-
-#: web/forms/accounts.py:266
-msgid "The password you entered is not correct."
-msgstr ""
-
-#: web/forms/accounts.py:268
-msgid "You must confirm your current password to make changes."
-msgstr ""
-
-#: web/forms/accounts.py:277
-msgid "New password"
-msgstr ""
-
-#: web/forms/accounts.py:334
-msgid "That username is already in use."
-msgstr ""
-
-#: web/forms/accounts.py:381
-msgid "Language"
-msgstr ""
-
-#: web/forms/accounts.py:384
-msgid "Stacktrace order"
-msgstr ""
-
-#: web/forms/accounts.py:385
-msgid "Default (let Sentry decide)"
-msgstr ""
-
-#: web/forms/accounts.py:386
-msgid "Most recent call last"
-msgstr ""
-
-#: web/forms/accounts.py:387
-msgid "Most recent call first"
-msgstr ""
-
-#: web/forms/accounts.py:388
-msgid "Choose the default ordering of frames in stacktraces."
-msgstr ""
-
-#: web/forms/accounts.py:392
-msgid "Time zone"
-msgstr ""
-
-#: web/forms/accounts.py:395
-msgid "Use a 24-hour clock"
-msgstr ""
-
-#: web/forms/accounts.py:484
-msgid "Designate an alternative email address to send email notifications to."
-msgstr ""
-
-#: web/forms/accounts.py:489
-msgid "Automatically subscribe to alerts for new projects"
-msgstr ""
-
-#: web/forms/accounts.py:490
-msgid ""
-"When enabled, you'll automatically subscribe to alerts when you create or "
-"join a project."
-msgstr ""
-
-#: web/forms/accounts.py:495
-msgid "Automatically subscribe to workflow notifications for new projects"
-msgstr ""
-
-#: web/forms/accounts.py:496
-msgid ""
-"When enabled, you'll automatically subscribe to workflow notifications when "
-"you create or join a project."
-msgstr ""
-
-#: web/forms/accounts.py:500
-msgid "Receive notifications about my own activity"
-msgstr ""
-
-#: web/forms/accounts.py:501
-msgid ""
-"Enable this if you wish to receive emails for your own actions, as well as "
-"others."
-msgstr ""
-
-#: web/forms/accounts.py:633
-msgid "One-time password"
-msgstr ""
-
-#: web/forms/accounts.py:634
-msgid "Code from authenticator"
-msgstr ""
-
-#: web/forms/accounts.py:642
-msgid "Sentry account password"
-msgstr ""
-
-#: web/forms/add_project.py:17
-msgid "i.e. API, Frontend, My Application Name"
-msgstr ""
-
-#: web/forms/add_project.py:19
-msgid "Using the repository name generally works well."
-msgstr ""
-
-#: web/forms/add_team.py:15
-msgid "E.g. Operations, Web, Desktop, ..."
-msgstr ""
-
-#: web/forms/fields.py:83
-msgid "Invalid username"
-msgstr ""
-
-#: web/forms/fields.py:105
-msgid "Not set"
-msgstr ""
-
-#: web/forms/fields.py:129
-msgid "e.g. example.com or https://example.com"
-msgstr ""
-
-#: web/forms/fields.py:158
-msgid "e.g. 127.0.0.1 or 10.0.0.0/8"
-msgstr ""
-
-#: web/forms/projects.py:20
-msgid "Minimum delivery frequency"
-msgstr ""
-
-#: static/sentry/app/views/projectAlertSettings.jsx:105
-#: web/forms/projects.py:21
-msgid "Notifications will be delivered at most this often."
-msgstr ""
-
-#: web/forms/projects.py:26
-msgid "Maximum delivery frequency"
-msgstr ""
-
-#: static/sentry/app/views/projectAlertSettings.jsx:119
-#: web/forms/projects.py:27
-msgid "Notifications will be delivered at least this often."
-msgstr ""
-
-#: web/forms/projects.py:35
-msgid ""
-"Maximum delivery frequency must be equal to or greater than the minimum "
-"delivery frequency."
-msgstr ""
-
-#: web/forms/projects.py:41
-msgid "Maximum events per minute"
-msgstr ""
-
-#: web/forms/projects.py:43
-msgid ""
-"This cannot be higher than the team (or system) allotted maximum. The value "
-"can be either a fixed number, or a percentage that is relative to the team's"
-" overall quota."
-msgstr ""
-
-#: web/forms/projects.py:80
-msgid "Label"
-msgstr ""
-
-#: web/frontend/accept_organization_invite.py:16
-msgid "The invite link you followed is not valid."
-msgstr ""
-
-#: web/frontend/accept_organization_invite.py:107
-#, python-format
-msgid "You are already a member of the %r organization."
-msgstr ""
-
-#: web/frontend/accept_organization_invite.py:129
-#, python-format
-msgid "You have been added to the %r organization."
-msgstr ""
-
-#: web/frontend/accounts.py:145
-msgid "There was an error confirming your email."
-msgstr ""
-
-#: web/frontend/accounts.py:149 web/frontend/accounts.py:156
-#, python-format
-msgid "A verification email has been sent to %s."
-msgstr ""
-
-#: web/frontend/accounts.py:158
-#, python-format
-msgid "Your email (%s) has already been verified."
-msgstr ""
-
-#: web/frontend/accounts.py:164
-msgid "Thanks for confirming your email"
-msgstr ""
-
-#: web/frontend/accounts.py:172
-msgid ""
-"There was an error confirming your email. Please try again or visit your "
-"Account Settings to resend the verification email."
-msgstr ""
-
-#: web/frontend/accounts.py:222 web/frontend/accounts.py:435
-#: web/frontend/accounts.py:457
-#, python-format
-msgid "A confirmation email has been sent to %s."
-msgstr ""
-
-#: web/frontend/accounts.py:229 web/frontend/accounts.py:464
-msgid "Your settings were saved."
-msgstr ""
-
-#: web/frontend/accounts_twofactor.py:26
-msgid "Phone number"
-msgstr ""
-
-#: web/frontend/accounts_twofactor.py:32
-msgid "Device name"
-msgstr ""
-
-#: web/frontend/auth_login.py:20
-msgid ""
-"The organization does not exist or does not have Single Sign-On enabled."
-msgstr ""
-
-#: web/frontend/create_organization.py:14
-msgid "Organization Name"
-msgstr ""
-
-#: web/frontend/create_organization.py:15
-msgid "My Company"
-msgstr ""
-
-#: web/frontend/create_organization_member.py:62
-#, python-format
-msgid "The organization member %s was added."
-msgstr ""
-
-#: web/frontend/create_organization_member.py:68
-#, python-format
-msgid "The organization member %s already exists."
-msgstr ""
-
-#: web/frontend/disable_project_key.py:32
-#, python-format
-msgid "The API key (%s) was disabled."
-msgstr ""
-
-#: web/frontend/edit_project_key.py:38
-#, python-format
-msgid "Changes to the API key (%s) were saved."
-msgstr ""
-
-#: web/frontend/enable_project_key.py:32
-#, python-format
-msgid "The API key (%s) was enabled."
-msgstr ""
-
-#: web/frontend/error_page_embed.py:23
-msgid ""
-"An unknown error occurred while submitting your report. Please try again."
-msgstr ""
-
-#: web/frontend/error_page_embed.py:24
-msgid "Some fields were invalid. Please correct the errors and try again."
-msgstr ""
-
-#: web/frontend/error_page_embed.py:25
-msgid "Your feedback has been sent. Thank you!"
-msgstr ""
-
-#: web/frontend/error_page_embed.py:30
-msgid "Jane Doe"
-msgstr ""
-
-#: web/frontend/error_page_embed.py:33
-msgid "jane@example.com"
-msgstr ""
-
-#: web/frontend/error_page_embed.py:37
-msgid "I clicked on 'X' and then hit 'Confirm'"
-msgstr ""
-
-#: web/frontend/organization_api_key_settings.py:15
-#: web/frontend/project_settings.py:30
-msgid "Allowed Domains"
-msgstr ""
-
-#: static/sentry/app/views/projectCspSettings.jsx:97
-#: web/frontend/organization_api_key_settings.py:16
-#: web/frontend/project_settings.py:31 web/frontend/project_settings.py:81
-msgid "Separate multiple entries with a newline."
-msgstr ""
-
-#: web/frontend/organization_auth_settings.py:21
-msgid "The SSO feature is not enabled for this organization."
-msgstr ""
-
-#: web/frontend/organization_auth_settings.py:23
-msgid "SSO authentication has been disabled."
-msgstr ""
-
-#: web/frontend/organization_auth_settings.py:25
-msgid ""
-"A reminder email has been sent to members who have not yet linked their "
-"accounts."
-msgstr ""
-
-#: web/frontend/organization_auth_settings.py:30
-msgid "Require SSO"
-msgstr ""
-
-#: web/frontend/organization_auth_settings.py:31
-msgid ""
-"Require members use a valid linked SSO account to access this organization"
-msgstr ""
-
-#: web/frontend/organization_auth_settings.py:35
-#: web/frontend/organization_settings.py:27
-msgid "Default Role"
-msgstr ""
-
-#: web/frontend/organization_auth_settings.py:37
-msgid ""
-"The default role new members will receive when logging in for the first "
-"time."
-msgstr ""
-
-#: web/frontend/organization_member_settings.py:36
-#, python-format
-msgid "A new invitation has been generated and sent to %(email)s"
-msgstr ""
-
-#: web/frontend/organization_member_settings.py:41
-#, python-format
-msgid "An invitation to join %(organization)s has been sent to %(email)s"
-msgstr ""
-
-#: web/frontend/organization_member_settings.py:93
-msgid "Your changes were saved."
-msgstr ""
-
-#: web/frontend/organization_settings.py:16
-msgid "The name of your organization. i.e. My Company"
-msgstr ""
-
-#: static/sentry/app/views/teamSettings.jsx:88
-#: web/frontend/organization_settings.py:18
-#: web/frontend/project_settings.py:26
-msgid "Short name"
-msgstr ""
-
-#: web/frontend/organization_settings.py:19
-msgid "A unique ID used to identify this organization."
-msgstr ""
-
-#: web/frontend/organization_settings.py:22
-msgid "Open Membership"
-msgstr ""
-
-#: web/frontend/organization_settings.py:23
-msgid "Allow organization members to freely join or leave any team."
-msgstr ""
-
-#: web/frontend/organization_settings.py:29
-msgid "The default role new members will receive."
-msgstr ""
-
-#: web/frontend/organization_settings.py:32
-msgid "Enhanced Privacy"
-msgstr ""
-
-#: web/frontend/organization_settings.py:33
-msgid ""
-"Enable enhanced privacy controls to limit personally identifiable "
-"information (PII) as well as source code in things like notifications."
-msgstr ""
-
-#: web/frontend/organization_settings.py:37
-msgid "Allow Shared Issues"
-msgstr ""
-
-#: web/frontend/organization_settings.py:38
-msgid "Enable sharing of limited details on issues to anonymous users."
-msgstr ""
-
-#: web/frontend/organization_settings.py:42
-msgid "Require Data Scrubber"
-msgstr ""
-
-#: web/frontend/organization_settings.py:43
-msgid "Require server-side data scrubbing be enabled for all projects."
-msgstr ""
-
-#: web/frontend/organization_settings.py:47
-msgid "Require Using Default Scrubbers"
-msgstr ""
-
-#: web/frontend/organization_settings.py:48
-msgid ""
-"Require the default scrubbers be applied to prevent things like passwords "
-"and credit cards from being stored for all projects."
-msgstr ""
-
-#: web/frontend/organization_settings.py:52
-msgid "Global additional sensitive fields"
-msgstr ""
-
-#: web/frontend/organization_settings.py:53
-msgid ""
-"Additional field names to match against when scrubbing data for all "
-"projects. Separate multiple entries with a newline. Note: These"
-" fields will be used in addition to project specific fields."
-msgstr ""
-
-#: web/frontend/organization_settings.py:56
-#: web/frontend/organization_settings.py:67
-#: web/frontend/project_settings.py:51 web/frontend/project_settings.py:62
-msgid "e.g. email"
-msgstr ""
-
-#: web/frontend/organization_settings.py:63
-msgid "Global safe fields"
-msgstr ""
-
-#: web/frontend/organization_settings.py:64
-msgid ""
-"Field names which data scrubbers should ignore. Separate multiple entries "
-"with a newline. Note: These fields will be used in addition to "
-"project specific fields."
-msgstr ""
-
-#: web/frontend/organization_settings.py:74
-msgid "Prevent Storing of IP Addresses"
-msgstr ""
-
-#: web/frontend/organization_settings.py:75
-msgid ""
-"Preventing IP addresses from being stored for new events on all projects."
-msgstr ""
-
-#: web/frontend/organization_settings.py:79
-msgid "Early Adopter"
-msgstr ""
-
-#: web/frontend/organization_settings.py:80
-msgid "Opt-in to new features before they're released to the public."
-msgstr ""
-
-#: web/frontend/organization_settings.py:163
-msgid "Changes to your organization were saved."
-msgstr ""
-
-#: web/frontend/project_quotas.py:12
-msgid "The quotas feature is not enabled for this project."
-msgstr ""
-
-#: web/frontend/project_release_tracking.py:21
-msgid ""
-"Your deploy token has been regenerated. You will need to update any pre-"
-"existing deploy hooks."
-msgstr ""
-
-#: web/frontend/project_release_tracking.py:23
-msgid "The release tracking feature is not enabled for this project."
-msgstr ""
-
-#: web/frontend/project_settings.py:23
-msgid "Project Name"
-msgstr ""
-
-#: web/frontend/project_settings.py:24
-msgid "Production"
-msgstr ""
-
-#: web/frontend/project_settings.py:27
-msgid "A unique ID used to identify this project."
-msgstr ""
-
-#: web/frontend/project_settings.py:32
-msgid "Security token"
-msgstr ""
-
-#: web/frontend/project_settings.py:33
-#, python-brace-format
-msgid ""
-"Outbound requests matching Allowed Domains will have the header \"X-Sentry-"
-"Token: {token}\" appended."
-msgstr ""
-
-#: web/frontend/project_settings.py:34
-msgid "Auto resolve"
-msgstr ""
-
-#: web/frontend/project_settings.py:36
-msgid ""
-"Automatically resolve an issue if it hasn't been seen for this amount of "
-"time."
-msgstr ""
-
-#: web/frontend/project_settings.py:38
-msgid "Data Scrubber"
-msgstr ""
-
-#: web/frontend/project_settings.py:39
-msgid "Enable server-side data scrubbing."
-msgstr ""
-
-#: web/frontend/project_settings.py:43
-msgid "Use Default Scrubbers"
-msgstr ""
-
-#: web/frontend/project_settings.py:44
-msgid ""
-"Apply default scrubbers to prevent things like passwords and credit cards "
-"from being stored."
-msgstr ""
-
-#: web/frontend/project_settings.py:48
-msgid "Additional sensitive fields"
-msgstr ""
-
-#: web/frontend/project_settings.py:49
-msgid ""
-"Additional field names to match against when scrubbing data. Separate "
-"multiple entries with a newline."
-msgstr ""
-
-#: web/frontend/project_settings.py:58
-msgid "Safe fields"
-msgstr ""
-
-#: web/frontend/project_settings.py:59
-msgid ""
-"Field names which data scrubbers should ignore. Separate multiple entries "
-"with a newline."
-msgstr ""
-
-#: web/frontend/project_settings.py:69
-msgid "Don't store IP Addresses"
-msgstr ""
-
-#: web/frontend/project_settings.py:70
-msgid "Prevent IP addresses from being stored for new events."
-msgstr ""
-
-#: web/frontend/project_settings.py:76
-msgid "Enable JavaScript source fetching"
-msgstr ""
-
-#: web/frontend/project_settings.py:77
-msgid ""
-"Allow Sentry to scrape missing JavaScript source context when possible."
-msgstr ""
-
-#: web/frontend/project_settings.py:80
-msgid "Filtered IP Addresses"
-msgstr ""
-
-#: web/frontend/project_settings.py:87
-msgid "Default Environment"
-msgstr ""
-
-#: web/frontend/project_settings.py:88
-msgid "The default selected environment when viewing issues."
-msgstr ""
-
-#: web/frontend/project_settings.py:89
-msgid "e.g. production"
-msgstr ""
-
-#: web/frontend/project_settings.py:93
-msgid "Subject Prefix"
-msgstr ""
-
-#: web/frontend/project_settings.py:94
-msgid "Choose a custom prefix for emails from this project."
-msgstr ""
-
-#: web/frontend/project_settings.py:276
-msgid "Changes to your project were saved."
-msgstr ""
-
-#: web/frontend/remove_organization.py:15
-msgid "You cannot remove the default organization."
-msgstr ""
-
-#: web/frontend/remove_organization.py:17
-#, python-format
-msgid "The %s organization has been scheduled for removal."
-msgstr ""
-
-#: web/frontend/remove_project.py:35
-#, python-format
-msgid "The project %r was scheduled for deletion."
-msgstr ""
-
-#: web/frontend/remove_project_key.py:37
-#, python-format
-msgid "The API key (%s) was revoked."
-msgstr ""
-
-#: web/frontend/remove_team.py:35
-#, python-format
-msgid "The team %r was scheduled for deletion."
-msgstr ""
-
-#: web/frontend/restore_organization.py:16
-msgid "Deletion already canceled."
-msgstr ""
-
-#: web/frontend/restore_organization.py:17
-msgid "Deletion cannot be canceled, already in progress"
-msgstr ""
-
-#: web/frontend/restore_organization.py:20
-msgid "Organization restored successfully."
-msgstr ""
-
-#: web/frontend/twofactor.py:41
-msgid "Invalid confirmation code. Try again."
-msgstr ""
-
-#: static/sentry/app/components/avatarSettings.jsx:60
-msgid "Successfully saved avatar preferences"
-msgstr ""
-
-#: static/sentry/app/components/avatarSettings.jsx:96
-msgid "Gravatars are managed through "
-msgstr ""
-
-#: static/sentry/app/components/avatarSettings.jsx:112
-msgid "Done"
-msgstr ""
-
-#: static/sentry/app/components/loadingError.jsx:12
-msgid "There was an error loading data."
-msgstr ""
-
-#: static/sentry/app/components/loadingError.jsx:27
-msgid "Retry"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeContainer.jsx:35
-msgid "You do not have enough permission to create new projects"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeContainer.jsx:46
-msgid "You do not have enough permission to create new teams"
-msgstr ""
-
-#: static/sentry/app/views/stream/savedSearchSelector.jsx:82
-msgid "Saving changes.."
-msgstr ""
-
-#: static/sentry/app/components/pluginConfig.jsx:80
-msgid "Unable to disable plugin. Please try again."
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:40
-msgid "[user] completed [dateCompleted]"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:47
-msgid "[user] kicked off [dateCompleted]"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:54
-msgid "[user] skipped [dateCompleted]"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:120
-msgid "Skip"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:119
-msgid "Need help?"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:120
-msgid "Ask us!"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:133
-msgid "Create a project"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:134
-msgid "Create your first Sentry project"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:142
-msgid "Send your first event"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:143
-msgid "Install Sentry's client and send an event"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:151
-msgid "Invite team member"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:152
-msgid "Bring your team aboard"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:160
-msgid "Add a second platform"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:161
-msgid "Add Sentry to a second platform"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:169
-msgid "Add user context"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:170
-msgid "Know who is being affected by crashes"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:178
-msgid "Set up release tracking"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:179
-msgid "See what releases are generating errors."
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:187
-msgid "Upload sourcemaps"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:188
-msgid "Deminify javascript stacktraces"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:205
-msgid "Set up issue tracking"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:206
-msgid "Link to Sentry issues within your issue tracker"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:214
-msgid "Set up an alerts service"
-msgstr ""
-
-#: static/sentry/app/components/todos.jsx:215
-msgid "Receive Sentry alerts in Slack or HipChat"
-msgstr ""
-
-#: static/sentry/app/components/timeSince.jsx:68
-#, python-format
-msgid "%(time)s old"
-msgstr ""
-
-#: static/sentry/app/views/myIssues/assignedToMe.jsx:12
-msgid "Assigned to me"
-msgstr ""
-
-#: static/sentry/app/views/organizationDashboard.jsx:37
-msgid "No issues have been assigned to you."
-msgstr ""
-
-#: static/sentry/app/components/sidebar/index.jsx:223
-msgid "My Bookmarks"
-msgstr ""
-
-#: static/sentry/app/components/sidebar/index.jsx:233
-msgid "You have no bookmarked issues."
-msgstr ""
-
-#: static/sentry/app/components/sidebar/index.jsx:240
-msgid "Recently Viewed"
-msgstr ""
-
-#: static/sentry/app/components/sidebar/index.jsx:250
-msgid "No recently viewed issues."
-msgstr ""
-
-#: static/sentry/app/components/sidebar/index.jsx:269
-msgid "Required Action:"
-msgstr ""
-
-#: static/sentry/app/components/projectHeader/projectSelector.jsx:279
-msgid "Select a project"
-msgstr ""
-
-#: static/sentry/app/components/projectHeader/projectSelector.jsx:287
-msgid "Filter projects"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:117
-msgid ""
-"There was an error saving your changes. Make sure all fields are valid and "
-"try again."
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:120
-msgid "Rule name"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:126
-msgid "My Rule Name"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:133
-#, python-format
-msgid "Every time %s of these conditions are met:"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:139
-msgid "all"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:140
-msgid "any"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:141
-msgid "none"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:148
-msgid ""
-"Ensure at least one condition is enabled and all required fields are filled "
-"in."
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:158
-msgid "Take these actions:"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:161
-msgid ""
-"Ensure at least one action is enabled and all required fields are filled in."
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:173
-msgid "Perform these actions at most once every [frequency] for an issue."
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:180
-msgid "5 minutes"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:181
-msgid "10 minutes"
-msgstr ""
-
-#: static/sentry/app/components/issues/snoozeAction.jsx:56
-msgid "30 minutes"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:183
-msgid "60 minutes"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:184
-msgid "3 hours"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:185
-msgid "12 hours"
-msgstr ""
-
-#: static/sentry/app/components/issues/snoozeAction.jsx:58
-msgid "24 hours"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:187
-msgid "one week"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:188
-msgid "30 days"
-msgstr ""
-
-#: static/sentry/app/views/ruleEditor/index.jsx:197
-msgid "Save Rule"
-msgstr ""
-
-#: static/sentry/app/components/avatarRadio.jsx:39
-msgid "Avatar Type"
-msgstr ""
-
-#: static/sentry/app/components/avatarCropper.jsx:358
-msgid "Change Photo"
-msgstr ""
-
-#: static/sentry/app/views/app.jsx:128
-msgid "Getting a list of all of your organizations."
-msgstr ""
-
-#: static/sentry/app/views/teamSettings.jsx:74
-msgid ""
-"Unable to save your changes. Please ensure all fields are valid and try "
-"again."
-msgstr ""
-
-#: static/sentry/app/views/apiNewToken.jsx:111
-msgid "Scopes"
-msgstr ""
-
-#: static/sentry/app/views/apiDashboard.jsx:175
-msgid "Create New Token"
-msgstr ""
-
-#: static/sentry/app/views/apiDashboard.jsx:155
-msgid ""
-"Authentication tokens allow you to perform actions against the Sentry API on"
-" behalf of your account. They're the easiest way to get started using the "
-"API."
-msgstr ""
-
-#: static/sentry/app/views/apiDashboard.jsx:156
-msgid ""
-"For more information on how to use the web API, see our "
-"[link:documentation]."
-msgstr ""
-
-#: static/sentry/app/views/adminSettings.jsx:149
-msgid "Your changes were saved, and will propagate to services shortly."
-msgstr ""
-
-#: static/sentry/app/views/installWizard.jsx:221
-msgid "Please wait while we load configuration."
-msgstr ""
-
-#: static/sentry/app/views/installWizard.jsx:226
-msgid ""
-"We were unable to load the required configuration from the Sentry server. "
-"Please take a look at the service logs."
-msgstr ""
-
-#: static/sentry/app/views/adminSettings.jsx:184
-msgid ""
-"We were unable to submit your changes to the Sentry server. Please take a "
-"look at the service logs."
-msgstr ""
-
-#: static/sentry/app/views/apiDashboard.jsx:127
-msgid "You haven't created any authentication tokens yet."
-msgstr ""
-
-#: static/sentry/app/views/apiDashboard.jsx:151
-msgid "Sentry Web API"
-msgstr ""
-
-#: static/sentry/app/views/apiDashboard.jsx:153
-msgid "Auth Tokens"
-msgstr ""
-
-#: static/sentry/app/views/groupHashes.jsx:81
-msgid "There don't seem to be any hashes for this issue."
-msgstr ""
-
-#: static/sentry/app/views/groupEvents.jsx:191
-msgid "ID"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails.jsx:163
-msgid "The issue you were looking for was not found."
-msgstr ""
-
-#: static/sentry/app/views/groupTags.jsx:102
-msgid "More Details"
-msgstr ""
-
-#: static/sentry/app/views/groupUserReports.jsx:109
-msgid "No user reports have been collected for this event."
-msgstr ""
-
-#: static/sentry/app/views/projectUserReports.jsx:162
-msgid "Learn how to integrate User Feedback"
-msgstr ""
-
-#: static/sentry/app/views/groupEvents.jsx:115
-msgid "Sorry, no events match your search query."
-msgstr ""
-
-#: static/sentry/app/views/groupEvents.jsx:124
-msgid "There don't seem to be any events yet."
-msgstr ""
-
-#: static/sentry/app/views/groupEvents.jsx:200
-msgid "User"
-msgstr ""
-
-#: static/sentry/app/views/groupEvents.jsx:236
-msgid "search event message or tags"
-msgstr ""
-
-#: static/sentry/app/views/groupTagValues.jsx:136
-msgid "Affected Users"
-msgstr ""
-
-#: static/sentry/app/views/groupTagValues.jsx:138
-msgid "Export to CSV"
-msgstr ""
-
-#: static/sentry/app/views/groupTagValues.jsx:153
-msgid "Note: Percentage of issue is based on events seen in the last 7 days."
-msgstr ""
-
-#: static/sentry/app/views/organizationDetails.jsx:134
-msgid "Loading data for your organization."
-msgstr ""
-
-#: static/sentry/app/views/organizationDetails.jsx:143
-msgid "The organization you were looking for was not found."
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/allTeamsList.jsx:41
-msgid ""
-"You don't have any teams for this organization yet. Get started by "
-"[link:creating your first team]."
-msgstr ""
-
-#: static/sentry/app/views/organizationAuditLog.jsx:127
-msgid "No results found."
-msgstr ""
-
-#: static/sentry/app/views/organizationAuditLog.jsx:164
-msgid "Any"
-msgstr ""
-
-#: static/sentry/app/views/organizationAuditLog.jsx:174
-msgid "Sentry keeps track of important events within your organization."
-msgstr ""
-
-#: static/sentry/app/views/organizationAuditLog.jsx:181
-msgid "Action"
-msgstr ""
-
-#: static/sentry/app/views/organizationAuditLog.jsx:182
-msgid "IP"
-msgstr ""
-
-#: static/sentry/app/views/organizationAuditLog.jsx:183
-msgid "Time"
-msgstr ""
-
-#: static/sentry/app/views/organizationDashboard.jsx:48
-msgid "View more"
-msgstr ""
-
-#: static/sentry/app/views/organizationDashboard.jsx:77
-msgid "No new issues have been seen in the last week."
-msgstr ""
-
-#: static/sentry/app/views/projectChooser.jsx:56
-msgid "Choose a project"
-msgstr ""
-
-#: static/sentry/app/plugins/components/settings.jsx:133
-msgid "An unknown error occurred. Need help with this? [link:Contact support]"
-msgstr ""
-
-#: static/sentry/app/views/organizationRepositories.jsx:361
-msgid "Add Repository"
-msgstr ""
-
-#: static/sentry/app/views/organizationRepositories.jsx:238
-msgid "Are you sure you want to remove this repository?"
-msgstr ""
-
-#: static/sentry/app/components/bases/pluginComponentBase.jsx:68
-msgid "An error occurred."
-msgstr ""
-
-#: static/sentry/app/components/bases/pluginComponentBase.jsx:100
-msgid "Unable to save changes. Please try again."
-msgstr ""
-
-#: static/sentry/app/views/projectAlertRules.jsx:64
-msgid "Edit Rule"
-msgstr ""
-
-#: static/sentry/app/views/projectAlertRules.jsx:180
-msgid "There are no alerts configured for this project."
-msgstr ""
-
-#: static/sentry/app/views/projectAlertSettings.jsx:242
-msgid "New Alert Rule"
-msgstr ""
-
-#: static/sentry/app/views/projectAlertSettings.jsx:249
-msgid "Rules"
-msgstr ""
-
-#: static/sentry/app/views/projectAlertSettings.jsx:79
-msgid "Digests"
-msgstr ""
-
-#: static/sentry/app/views/projectAlertSettings.jsx:104
-msgid "Minimum delivery interval"
-msgstr ""
-
-#: static/sentry/app/views/projectAlertSettings.jsx:118
-msgid "Maximum delivery interval"
-msgstr ""
-
-#: static/sentry/app/views/projectCspSettings.jsx:87
-msgid "Use Default Ignored Sources"
-msgstr ""
-
-#: static/sentry/app/views/projectCspSettings.jsx:88
-msgid ""
-"Our default list will attempt to ignore common issues and reduce noise."
-msgstr ""
-
-#: static/sentry/app/views/projectCspSettings.jsx:96
-msgid "Additional Ignored Sources"
-msgstr ""
-
-#: static/sentry/app/views/projectUserReportSettings.jsx:281
-msgid "Integration"
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/overview.jsx:57
-msgid "Configure your application"
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/overview.jsx:59
-msgid ""
-"Get started by selecting the platform or language that powers your "
-"application."
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/overview.jsx:71
-msgid "Public DSN"
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/overview.jsx:73
-msgid "Your public DSN should be used with JavaScript and ActionScript."
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/overview.jsx:77
-msgid "Already have things setup? [link:Get your DSN]."
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/overview.jsx:82
-msgid "Popular"
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/overview.jsx:94
-msgid "Frameworks"
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/overview.jsx:102
-msgid "Languages"
-msgstr ""
-
-#: static/sentry/app/views/projectDebugSymbols.jsx:72
-msgid "There are no debug symbols for this project."
-msgstr ""
-
-#: static/sentry/app/views/projectDebugSymbols.jsx:82
-msgid "UUID"
-msgstr ""
-
-#: static/sentry/app/views/projectDebugSymbols.jsx:83
-msgid "Object Name"
-msgstr ""
-
-#: static/sentry/app/views/projectDebugSymbols.jsx:84
-msgid "Type"
-msgstr ""
-
-#: static/sentry/app/views/projectDebugSymbols.jsx:85
-msgid "Upload Date"
-msgstr ""
-
-#: static/sentry/app/views/projectDebugSymbols.jsx:86
-msgid "Size"
-msgstr ""
-
-#: static/sentry/app/views/projectFilters.jsx:169
-msgid "Inbound Data Filters"
-msgstr ""
-
-#: static/sentry/app/views/projectUserReports.jsx:152
-msgid "Sorry, no results match your search query."
-msgstr ""
-
-#: static/sentry/app/views/projectUserReports.jsx:161
-msgid "No user reports have been collected for this project."
-msgstr ""
-
-#: static/sentry/app/components/organizationIssueList.jsx:53
-msgid "All Issues"
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/platform.jsx:103
-msgid "Generic"
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/platform.jsx:124
-msgid "Full Documentation"
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/platform.jsx:127
-#, python-format
-msgid "Configure %(integration)s"
-msgstr ""
-
-#: static/sentry/app/views/projectInstall/platform.jsx:156
-msgid "Got it! Take me to the Issue Stream."
-msgstr ""
-
-#: static/sentry/app/views/releaseAllEvents.jsx:21
-msgid "View all events seen in this release in the stream"
-msgstr ""
-
-#: static/sentry/app/views/releaseNewEvents.jsx:21
-msgid "View new events seen in this release in the stream"
-msgstr ""
-
-#: static/sentry/app/views/releaseArtifacts.jsx:71
-msgid "Removing artifact.."
-msgstr ""
-
-#: static/sentry/app/views/releaseArtifacts.jsx:84
-msgid "Artifact removed."
-msgstr ""
-
-#: static/sentry/app/views/releaseArtifacts.jsx:89
-msgid "Unable to remove artifact. Please try again."
-msgstr ""
-
-#: static/sentry/app/views/releaseArtifacts.jsx:108
-msgid "There are no artifacts uploaded for this release."
-msgstr ""
-
-#: static/sentry/app/views/releaseArtifacts.jsx:131
-msgid "Delete artifact"
-msgstr ""
-
-#: static/sentry/app/views/releaseArtifacts.jsx:132
-msgid "Are you sure you want to remove this artifact?"
-msgstr ""
-
-#: static/sentry/app/components/group/seenInfo.jsx:85
-msgid "Release"
-msgstr ""
-
-#: static/sentry/app/views/projectDashboard.jsx:166
-msgid "New Issues"
-msgstr ""
-
-#: static/sentry/app/views/releaseDetails.jsx:115
-msgid "First Event"
-msgstr ""
-
-#: static/sentry/app/views/projectReleases/index.jsx:174
-msgid "Last Event"
-msgstr ""
-
-#: static/sentry/app/views/releaseDetails.jsx:141
-msgid "Artifacts"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:232
-msgid "More"
-msgstr ""
-
-#: static/sentry/app/views/projectUserReportSettings.jsx:87
-msgid "Show Sentry Branding"
-msgstr ""
-
-#: static/sentry/app/views/projectUserReportSettings.jsx:88
-msgid ""
-"Show \"powered by Sentry\" within the feedback dialog. We appreciate you "
-"helping get the word out about Sentry! <3"
-msgstr ""
-
-#: static/sentry/app/views/projectDashboard.jsx:128
-msgid "1 hour"
-msgstr ""
-
-#: static/sentry/app/views/projectDashboard.jsx:149
-msgid "1 week"
-msgstr ""
-
-#: static/sentry/app/views/projectDashboard.jsx:161
-msgid "Trending Issues"
-msgstr ""
-
-#: static/sentry/app/views/projectSavedSearches.jsx:207
-msgid "There are no saved searches for this project."
-msgstr ""
-
-#: static/sentry/app/views/teamSettings.jsx:81
-msgid "e.g. API Team"
-msgstr ""
-
-#: static/sentry/app/views/stream.jsx:509
-msgid "Or see a sample Javascript event"
-msgstr ""
-
-#: static/sentry/app/views/stream.jsx:516
-msgid "Waiting for events…"
-msgstr ""
-
-#: static/sentry/app/views/stream.jsx:517
-msgid "Our error robot is waiting to [cross:devour] receive your first event."
-msgstr ""
-
-#: static/sentry/app/views/stream.jsx:518
-msgid "Installation Instructions"
-msgstr ""
-
-#: static/sentry/app/views/projectEvents/index.jsx:130
-msgid "Sorry, no events match your filters."
-msgstr ""
-
-#: static/sentry/app/views/requiredAdminActions/setCallsigns.jsx:86
-msgid "Failed to set slugs"
-msgstr ""
-
-#: static/sentry/app/views/requiredAdminActions/setCallsigns.jsx:206
-msgid "Review Call Signs for Projects"
-msgstr ""
-
-#: static/sentry/app/views/requiredAdminActions/setCallsigns.jsx:148
-#, python-format
-msgid ""
-"Sentry now requires you to specify a call sign (short name) for each project"
-" in the organization “%s”. These short names are used to identify the "
-"project in the issue IDs. Ideally they are two or three letter long."
-msgstr ""
-
-#: static/sentry/app/views/requiredAdminActions/setCallsigns.jsx:150
-msgid "Projects of teams you are not a member of are not shown."
-msgstr ""
-
-#: static/sentry/app/views/requiredAdminActions/setCallsigns.jsx:151
-msgid "Projects which have been previously reviewed are shown in green."
-msgstr ""
-
-#: static/sentry/app/views/requiredAdminActions/setCallsigns.jsx:190
-msgid "Set Call Signs"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeSidebar.jsx:35
-msgid "Organization"
-msgstr ""
-
-#: static/sentry/app/components/sidebar/broadcasts.jsx:121
-msgid "Recent updates from Sentry"
-msgstr ""
-
-#: static/sentry/app/components/sidebar/broadcasts.jsx:126
-msgid "No recent updates from the Sentry team."
-msgstr ""
-
-#: static/sentry/app/components/u2finterface.jsx:133
-msgid "Support"
-msgstr ""
-
-#: static/sentry/app/components/u2finterface.jsx:136
-msgid "Error: "
-msgstr ""
-
-#: static/sentry/app/components/u2finterface.jsx:137
-msgid "Your U2F device reported an error."
-msgstr ""
-
-#: static/sentry/app/components/u2finterface.jsx:138
-msgid "This device is already in use."
-msgstr ""
-
-#: static/sentry/app/components/u2finterface.jsx:139
-msgid "The device you used for sign-in is unknown."
-msgstr ""
-
-#: static/sentry/app/components/u2finterface.jsx:154
-msgid "Try Again"
-msgstr ""
-
-#: static/sentry/app/components/activity/feed.jsx:122
-msgid "Nothing to show here, move along."
-msgstr ""
-
-#: static/sentry/app/components/sidebar/incidents.jsx:41
-msgid "Recent status updates"
-msgstr ""
-
-#: static/sentry/app/views/organizationRateLimits/index.jsx:36
-msgid "Saving.."
-msgstr ""
-
-#: static/sentry/app/views/organizationRateLimits/index.jsx:71
-msgid ""
-"Your organization is limited to [strong:[maxRate] events per minute]. When "
-"this rate is exceeded the system will begin discarding data until the next "
-"interval."
-msgstr ""
-
-#: static/sentry/app/views/organizationRateLimits/index.jsx:80
-msgid "Max percentage a single project may send"
-msgstr ""
-
-#: static/sentry/app/views/organizationRateLimits/index.jsx:85
-msgid ""
-"The maximum percentage of your quota an individual project can consume."
-msgstr ""
-
-#: static/sentry/app/views/organizationRateLimits/index.jsx:91
-msgid "Apply Changes"
-msgstr ""
-
-#: static/sentry/app/views/organizationRateLimits/index.jsx:119
-msgid "There are no rate limits configured for your organization."
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/index.jsx:74
-msgid "Your Teams"
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/index.jsx:75
-msgid "All Teams"
-msgstr ""
-
-#: static/sentry/app/views/organizationStats/index.jsx:255
-msgid "Events per minute"
-msgstr ""
-
-#: static/sentry/app/views/organizationStats/index.jsx:279
-msgid "Events by Project"
-msgstr ""
-
-#: static/sentry/app/views/projectEvents/index.jsx:139
-msgid "There don't seem to be any events."
-msgstr ""
-
-#: static/sentry/app/views/projectReleases/index.jsx:135
-msgid "Sorry, no releases match your filters."
-msgstr ""
-
-#: static/sentry/app/views/projectReleases/index.jsx:144
-msgid "There don't seem to be any releases yet."
-msgstr ""
-
-#: static/sentry/app/views/projectReleases/index.jsx:146
-msgid "Learn how to integrate Release Tracking"
-msgstr ""
-
-#: static/sentry/app/components/projectHeader/index.jsx:56
-msgid "Releases"
-msgstr ""
-
-#: static/sentry/app/views/projectReleases/index.jsx:161
-msgid "Search for a release."
-msgstr ""
-
-#: static/sentry/app/views/projectReleases/index.jsx:169
-msgid "Version"
-msgstr ""
-
-#: static/sentry/app/views/projectReleases/index.jsx:171
-msgid "New Events"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:37
-#, python-format
-msgid "%s left a comment"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:39
-#, python-format
-msgid "%s marked this issue as resolved"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:41
-#, python-format
-msgid "%(author)s marked this issue as resolved due to inactivity"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:46
-#, python-format
-msgid "%(author)s marked this issue as resolved in %(version)s"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:51
-#, python-format
-msgid "%s marked this issue as resolved in the upcoming release"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:54
-#, python-format
-msgid "%s marked this issue as unresolved"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:57
-#, python-format
-msgid "%(author)s ignored this issue for %(duration)s"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:62
-#, python-format
-msgid "%s ignored this issue"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:64
-#, python-format
-msgid "%s made this issue public"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:66
-#, python-format
-msgid "%s made this issue private"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:69
-#, python-format
-msgid "%(author)s marked this issue as a regression in %(version)s"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:74
-#, python-format
-msgid "%s marked this issue as a regression"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:77
-#, python-format
-msgid "%(author)s created an issue on %(provider)s titled %(title)s"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:83
-#, python-format
-msgid "%s first saw this issue"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:88
-#, python-format
-msgid "%s assigned this event to themselves"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:100
-#, python-format
-msgid "%(author)s assigned this event to %(assignee)s"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:97
-#, python-format
-msgid "%s assigned this event to an unknown user"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:105
-#, python-format
-msgid "%s unassigned this issue"
-msgstr ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:107
-msgid "%2$s merged %1$d issue into this issue"
-msgid_plural "%2$s merged %1$d issues into this issue"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/groupActivity/index.jsx:126
-msgid "Removing comment.."
-msgstr ""
-
-#: static/sentry/app/views/installWizard.jsx:183
-#, python-format
-msgid ""
-"An invalid option (%s) was passed to the server. Please report this issue to"
-" the Sentry team."
-msgstr ""
-
-#: static/sentry/app/views/installWizard.jsx:187
-#, python-format
-msgid "An invalid value for (%s) was passed to the server."
-msgstr ""
-
-#: static/sentry/app/views/installWizard.jsx:191
-msgid "An unknown error occurred. Please take a look at the service logs."
-msgstr ""
-
-#: static/sentry/app/views/installWizard.jsx:211
-msgid "Sentry Setup"
-msgstr ""
-
-#: static/sentry/app/views/installWizard.jsx:216
-msgid "Welcome to Sentry"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/eventToolbar.jsx:89
-msgid "Oldest"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/eventToolbar.jsx:103
-msgid "Older"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/eventToolbar.jsx:112
-msgid "Newer"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/eventToolbar.jsx:119
-msgid "Newest"
-msgstr ""
-
-#: static/sentry/app/components/groupListHeader.jsx:10
-msgid "Event"
-msgstr ""
-
-#: static/sentry/app/components/mutedBox.jsx:21
-#, python-format
-msgid "This issue has been ignored until %s"
-msgstr ""
-
-#: static/sentry/app/components/mutedBox.jsx:26
-msgid "This issue has been ignored"
-msgstr ""
-
-#: static/sentry/app/components/mutedBox.jsx:28
-msgid ""
-"You will not be notified of any changes and it will not show up by default "
-"in feeds."
-msgstr ""
-
-#: static/sentry/app/options.jsx:14
-msgid "Outbound email"
-msgstr ""
-
-#: static/sentry/app/options.jsx:22
-msgid "Root URL"
-msgstr ""
-
-#: static/sentry/app/options.jsx:24
-msgid ""
-"The root web address which is used to communicate with the Sentry backend."
-msgstr ""
-
-#: static/sentry/app/options.jsx:29
-msgid "Admin Email"
-msgstr ""
-
-#: static/sentry/app/options.jsx:31
-msgid "The technical contact for this Sentry installation."
-msgstr ""
-
-#: static/sentry/app/options.jsx:38
-msgid "Rate Limit"
-msgstr ""
-
-#: static/sentry/app/options.jsx:40
-msgid ""
-"The maximum number of events the system should accept per minute. A value of"
-" 0 will disable the default rate limit."
-msgstr ""
-
-#: static/sentry/app/options.jsx:44
-msgid "IP Rate Limit"
-msgstr ""
-
-#: static/sentry/app/options.jsx:46
-msgid ""
-"The maximum number of times an authentication attempt may be made by a "
-"single IP address in a 60 second window."
-msgstr ""
-
-#: static/sentry/app/options.jsx:50
-msgid "User Rate Limit"
-msgstr ""
-
-#: static/sentry/app/options.jsx:52
-msgid ""
-"The maximum number of times an authentication attempt may be made against a "
-"single account in a 60 second window."
-msgstr ""
-
-#: static/sentry/app/options.jsx:58
-msgid ""
-"The maximum number of organizations which may be created by a single account"
-" in a one hour window."
-msgstr ""
-
-#: static/sentry/app/options.jsx:62
-msgid "Email From"
-msgstr ""
-
-#: static/sentry/app/options.jsx:65
-msgid "Email address to be used in From for all outbound email."
-msgstr ""
-
-#: static/sentry/app/options.jsx:69
-msgid "SMTP Host"
-msgstr ""
-
-#: static/sentry/app/options.jsx:75
-msgid "SMTP Port"
-msgstr ""
-
-#: static/sentry/app/options.jsx:81
-msgid "SMTP Username"
-msgstr ""
-
-#: static/sentry/app/options.jsx:86
-msgid "SMTP Password"
-msgstr ""
-
-#: static/sentry/app/options.jsx:95
-msgid "Use TLS?"
-msgstr ""
-
-#: static/sentry/app/components/events/eventEntries.jsx:92
-msgid "There was an error rendering this data."
-msgstr ""
-
-#: static/sentry/app/components/events/eventEntries.jsx:121
-#, python-format
-msgid "This event was reported with an old version of the %s SDK."
-msgstr ""
-
-#: static/sentry/app/components/events/eventEntries.jsx:124
-msgid "Learn More"
-msgstr ""
-
-#: static/sentry/app/components/group/sidebar.jsx:26
-msgid "You're receiving updates because you have commented on this issue."
-msgstr ""
-
-#: static/sentry/app/components/group/sidebar.jsx:27
-msgid "You're receiving updates because you were assigned to this issue."
-msgstr ""
-
-#: static/sentry/app/components/group/sidebar.jsx:28
-msgid "You're receiving updates because you have bookmarked this issue."
-msgstr ""
-
-#: static/sentry/app/components/group/sidebar.jsx:29
-msgid ""
-"You're receiving updates because you have changed the status of this issue."
-msgstr ""
-
-#: static/sentry/app/components/group/sidebar.jsx:72
-msgid "External Issues"
-msgstr ""
-
-#: static/sentry/app/components/group/sidebar.jsx:83
-msgid "You're receiving updates because you are subscribed to this issue."
-msgstr ""
-
-#: static/sentry/app/components/group/sidebar.jsx:90
-msgid ""
-"You're receiving updates because you are [link:subscribed to workflow "
-"notifications] for this project."
-msgstr ""
-
-#: static/sentry/app/components/group/sidebar.jsx:96
-msgid "You're not subscribed to this issue."
-msgstr ""
-
-#: static/sentry/app/components/group/sidebar.jsx:137
-msgid "Subscribe"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/header.jsx:148
-msgid "Issue #"
-msgstr ""
-
-#: static/sentry/app/views/stream/searchBar.jsx:82
-msgid "Assigned"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/header.jsx:181
-msgid "Share this event"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/header.jsx:206
-msgid "Related Events"
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/allTeamsRow.jsx:50
-msgid "There was an error while trying to join the team."
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/allTeamsRow.jsx:78
-msgid "There was an error while trying to leave the team."
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/expandedTeamList.jsx:84
-msgid "Leave Team"
-msgstr ""
-
-#: static/sentry/app/components/missingProjectMembership.jsx:70
-msgid "Request Pending"
-msgstr ""
-
-#: static/sentry/app/components/missingProjectMembership.jsx:73
-msgid "Join Team"
-msgstr ""
-
-#: static/sentry/app/components/missingProjectMembership.jsx:76
-msgid "Request Access"
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/expandedTeamList.jsx:88
-msgid "Team Settings"
-msgstr ""
-
-#: static/sentry/app/stores/groupStore.jsx:220
-msgid "Unable to change assignee. Please try again."
-msgstr ""
-
-#: static/sentry/app/stores/groupStore.jsx:244
-msgid "Unable to delete events. Please try again."
-msgstr ""
-
-#: static/sentry/app/stores/groupStore.jsx:255
-msgid "The selected events have been scheduled for deletion."
-msgstr ""
-
-#: static/sentry/app/stores/groupStore.jsx:274
-msgid "Unable to merge events. Please try again."
-msgstr ""
-
-#: static/sentry/app/stores/groupStore.jsx:291
-msgid "The selected events have been scheduled for merge."
-msgstr ""
-
-#: static/sentry/app/stores/groupStore.jsx:323
-msgid "Unable to update events. Please try again."
-msgstr ""
-
-#: static/sentry/app/components/bases/pluginComponentBase.jsx:90
-msgid "Success!"
-msgstr ""
-
-#: static/sentry/app/components/events/eventsPerHour.jsx:124
-msgid "View Stats"
-msgstr ""
-
-#: static/sentry/app/components/events/eventsPerHour.jsx:125
-msgid "Events Per Hour"
-msgstr ""
-
-#: static/sentry/app/views/projects/projectContext.jsx:216
-msgid "The project you were looking for was not found."
-msgstr ""
-
-#: static/sentry/app/components/groupList.jsx:125
-msgid "There doesn't seem to be any events fitting the query."
-msgstr ""
-
-#: static/sentry/app/views/stream/actionLink.jsx:110
-msgid "Please confirm"
-msgstr ""
-
-#: static/sentry/app/views/projectDashboard/eventList.jsx:108
-msgid "No data available."
-msgstr ""
-
-#: static/sentry/app/views/stream/sidebar.jsx:107
-msgid "Text"
-msgstr ""
-
-#: static/sentry/app/views/stream/sidebar.jsx:111
-msgid "Search title and culprit text body"
-msgstr ""
-
-#: static/sentry/app/views/stream/filters.jsx:76
-msgid "Search for events, users, tags, and everything else."
-msgstr ""
-
-#: static/sentry/app/components/actionOverlay.jsx:56
-msgid "Do this later …"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:93
-msgid "Removing events.."
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:110
-msgid "Merging events.."
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:150
-msgid "This will apply to the current search query:"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:154
-msgid "This will apply to ALL issues in this project!"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:151
-msgid "Resolve"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:176
-msgid ""
-"Are you sure you want to resolve all issues matching this search query?"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:178
-#, python-format
-msgid "Are you sure you want to resolve this %d issue?"
-msgid_plural "Are you sure you want to resolve these %d issues?"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:184
-msgid "Resolve all issues"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:186
-#, python-format
-msgid "Resolve %d selected issue"
-msgid_plural "Resolve %d selected issues"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:190
-msgid "Set Status to Resolved"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:214
-msgid "Bookmark"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:203
-msgid ""
-"Are you sure you want to bookmark all issues matching this search query?"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:205
-#, python-format
-msgid "Are you sure you want to bookmark this %d issue?"
-msgid_plural "Are you sure you want to bookmark these %d issues?"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:211
-msgid "Bookmark all issues"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:213
-#, python-format
-msgid "Bookmark %d selected issue"
-msgid_plural "Bookmark %d selected issues"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:217
-msgid "Add to Bookmarks"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:237
-msgid "Are you sure you want to merge all issues matching this search query?"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:239
-#, python-format
-msgid "Are you sure you want to merge %d issue?"
-msgid_plural "Are you sure you want to merge %d issues?"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:245
-msgid "Merge all issues"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:247
-#, python-format
-msgid "Merge %d selected issue"
-msgid_plural "Merge %d selected issues"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:252
-msgid "Merge Events"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:263
-msgid ""
-"Are you sure you want to remove all issues matching this search query from "
-"your bookmarks?"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:265
-#, python-format
-msgid "Are you sure you want to remove this %d issue from your bookmarks?"
-msgid_plural ""
-"Are you sure you want to remove these %d issues from your bookmarks?"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:271
-msgid "Remove all issues from bookmarks"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:273
-#, python-format
-msgid "Remove %d selected issue from bookmarks"
-msgid_plural "Remove %d selected issues from bookmarks"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:279
-msgid "Remove from Bookmarks"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:291
-msgid ""
-"Are you sure you want to unresolve all issues matching this search query?"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:293
-#, python-format
-msgid "Are you sure you want to unresolve this %d issue?"
-msgid_plural "Are you sure you want to unresolve these %d issues?"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:299
-msgid "Unresolve all issues"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:301
-#, python-format
-msgid "Unresolve %d selected issue"
-msgid_plural "Unresolve %d selected issues"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:308
-msgid "Set status to: Unresolved"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:319
-msgid "Are you sure you want to ignore all issues matching this search query?"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:321
-#, python-format
-msgid "Are you sure you want to ignore this %d issue?"
-msgid_plural "Are you sure you want to ignore these %d issues?"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:327
-msgid "Ignore all issues"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:329
-#, python-format
-msgid "Ignore %d selected issue"
-msgid_plural "Ignore %d selected issues"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:335
-msgid "Set status to: Ignored"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:347
-#, python-format
-msgid "Are you sure you want to delete %d issue?"
-msgid_plural "Are you sure you want to delete %d issues?"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:353
-#, python-format
-msgid "Delete %d selected issue"
-msgid_plural "Delete %d selected issues"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:358
-msgid "Delete Events"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:378
-msgid "Graph:"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:381
-msgid "24h"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:384
-msgid "14d"
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:395
-msgid "All issues matching this search query selected."
-msgstr ""
-
-#: static/sentry/app/views/stream/actions.jsx:397
-#, python-format
-msgid "%d issue on this page selected."
-msgid_plural "%d issues on this page selected."
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/views/stream/actions.jsx:400
-msgid "Select all issues matching this search query."
-msgstr ""
-
-#: static/sentry/app/components/projectHeader/index.jsx:67
-msgid "Unstar Project"
-msgstr ""
-
-#: static/sentry/app/components/projectHeader/index.jsx:69
-msgid "Star Project"
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/expandedTeamList.jsx:65
-msgid ""
-"There are no projects in this team. Get started by [link:creating your first"
-" project]."
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/expandedTeamList.jsx:149
-msgid ""
-"You are not a member of any teams. [joinLink:Join an existing team] or "
-"[createLink:create a new one]."
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/expandedTeamList.jsx:154
-msgid "You are not a member of any teams. [joinLink:Join a team]."
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/expandedTeamList.jsx:164
-msgid ""
-"You dont have any teams for this organization yet. Get started by "
-"[link:creating your first team]."
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/organizationStatOverview.jsx:87
-msgid "Events Per Minute"
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/organizationStatOverview.jsx:89
-msgid "Rejected in last 24h"
-msgstr ""
-
-#: static/sentry/app/views/organizationTeams/organizationStatOverview.jsx:93
-msgid "View all stats"
-msgstr ""
-
-#: static/sentry/app/views/organizationStats/projectTable.jsx:47
-msgid "Accepted"
-msgstr ""
-
-#: static/sentry/app/views/organizationStats/projectTable.jsx:49
-msgid "Dropped"
-msgstr ""
-
-#: static/sentry/app/views/organizationStats/projectTable.jsx:48
-msgid "(Rate Limit)"
-msgstr ""
-
-#: static/sentry/app/views/organizationStats/projectTable.jsx:49
-msgid "(Filters)"
-msgstr ""
-
-#: static/sentry/app/views/organizationStats/projectTable.jsx:50
-msgid "Total"
-msgstr ""
-
-#: static/sentry/app/components/activity/noteInput.jsx:15
-msgid "Unknown error. Please try again."
-msgstr ""
-
-#: static/sentry/app/components/activity/noteInput.jsx:106
-msgid "Posting comment.."
-msgstr ""
-
-#: static/sentry/app/components/activity/noteInput.jsx:140
-msgid "Updating comment.."
-msgstr ""
-
-#: static/sentry/app/components/activity/noteInput.jsx:220
-msgid "Save Comment"
-msgstr ""
-
-#: static/sentry/app/components/activity/noteInput.jsx:220
-msgid "Post Comment"
-msgstr ""
-
-#: static/sentry/app/components/activity/note.jsx:32
-msgid "Edit"
-msgstr ""
-
-#: static/sentry/app/components/activity/noteInput.jsx:227
-msgid "Write"
-msgstr ""
-
-#: static/sentry/app/components/activity/noteInput.jsx:230
-msgid "Preview"
-msgstr ""
-
-#: static/sentry/app/components/activity/noteInput.jsx:234
-msgid "Markdown supported"
-msgstr ""
-
-#: static/sentry/app/components/activity/noteInput.jsx:242
-msgid "Add details or updates to this event"
-msgstr ""
-
-#: static/sentry/app/plugins/components/settings.jsx:124
-msgid "Associate Identity"
-msgstr ""
-
-#: static/sentry/app/plugins/components/issueActions.jsx:86
-msgid "An unknown error occurred."
-msgstr ""
-
-#: static/sentry/app/plugins/components/issueActions.jsx:137
-msgid "There was an error creating the issue."
-msgstr ""
-
-#: static/sentry/app/plugins/components/issueActions.jsx:148
-msgid "There was an error linking the issue."
-msgstr ""
-
-#: static/sentry/app/plugins/components/issueActions.jsx:158
-msgid "There was an error unlinking the issue."
-msgstr ""
-
-#: static/sentry/app/plugins/components/issueActions.jsx:231
-msgid "Are you sure you want to unlink this issue?"
-msgstr ""
-
-#: static/sentry/app/components/group/issuePluginActions.jsx:43
-msgid "Unlink Issue"
-msgstr ""
-
-#: static/sentry/app/components/events/errors.jsx:43
-msgid "Hide"
-msgstr ""
-
-#: static/sentry/app/components/events/errors.jsx:43
-msgid "Show"
-msgstr ""
-
-#: static/sentry/app/components/events/errors.jsx:45
-#, python-format
-msgid "There was %d error encountered while processing this event"
-msgid_plural "There were %d errors encountered while processing this event"
-msgstr[0] ""
-msgstr[1] ""
-
-#: static/sentry/app/components/events/sdk.jsx:22
-msgid "SDK"
-msgstr ""
-
-#: static/sentry/app/components/events/extraData.jsx:27
-msgid "Additional Data"
-msgstr ""
-
-#: static/sentry/app/components/events/device.jsx:37
-msgid "Device"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/message.jsx:23
-msgid "Message"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/message.jsx:29
-msgid "Params"
-msgstr ""
-
-# this means "rich" rendering (fancy tables)
-#: static/sentry/app/components/events/interfaces/request.jsx:73
-msgid "Rich"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/template.jsx:25
-msgid "Template"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/csp.jsx:54
-msgid "Report"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/crashHeader.jsx:110
-msgid "Raw"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/csp.jsx:56
-msgid "Help"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/csp.jsx:58
-msgid "CSP Report"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/breadcrumbs.jsx:76
-msgid "Sorry, no breadcrumbs match your search query."
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/breadcrumbs.jsx:145
-msgid "Search breadcrumbs..."
-msgstr ""
-
-#: static/sentry/app/components/events/contextSummary.jsx:46
-msgid "Version:"
-msgstr ""
-
-#: static/sentry/app/components/events/contextSummary.jsx:46
-msgid "Unknown"
-msgstr ""
-
-#: static/sentry/app/components/events/contextSummary.jsx:69
-msgid "Unknown User"
-msgstr ""
-
-#: static/sentry/app/components/events/contextSummary.jsx:100
-msgid "Unknown Device"
-msgstr ""
-
-#: static/sentry/app/components/events/contextSummary.jsx:172
-msgid "Unknown OS"
-msgstr ""
-
-#: static/sentry/app/components/events/contextSummary.jsx:146
-msgid "Unknown Browser"
-msgstr ""
-
-#: static/sentry/app/components/events/contextSummary.jsx:165
-msgid "Unknown Runtime"
-msgstr ""
-
-#: static/sentry/app/components/group/releaseStats.jsx:183
-msgid "Last 24 Hours"
-msgstr ""
-
-#: static/sentry/app/components/group/releaseStats.jsx:194
-msgid "Last 30 Days"
-msgstr ""
-
-#: static/sentry/app/components/group/releaseChart.jsx:120
-msgid "First seen"
-msgstr ""
-
-#: static/sentry/app/components/group/releaseChart.jsx:131
-msgid "Last seen"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/stacktrace.jsx:57
-msgid "Stacktrace"
-msgstr ""
-
-#: static/sentry/app/components/missingProjectMembership.jsx:62
-#, python-format
-msgid "To view this data you must first join the %s team."
-msgstr ""
-
-#: static/sentry/app/components/missingProjectMembership.jsx:64
-#, python-format
-msgid "To view this data you must first request access to the %s team."
-msgstr ""
-
-#: static/sentry/app/components/group/tagDistributionMeter.jsx:122
-msgid "Other"
-msgstr ""
-
-#: static/sentry/app/components/group/tagDistributionMeter.jsx:135
-msgid "No recent data."
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:58
-msgid "[author] commented on [link:an issue]"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:63
-msgid "[author] marked [link:an issue] as resolved"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:68
-msgid "[author] marked [link:an issue] as resolved due to age"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:74
-msgid "[author] marked [link:an issue] as resolved in [version]"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:80
-msgid "[author] marked [link:an issue] as resolved in the upcoming release"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:85
-msgid "[author] marked [link:an issue] as unresolved"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:91
-msgid "[author] ignored [link:an issue] for [duration]"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:97
-msgid "[author] ignored [link:an issue]"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:102
-msgid "[author] made an [link:an issue] public"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:107
-msgid "[author] made an [link:an issue] private"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:113
-msgid "[author] marked [link:an issue] as a regression in [version]"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:119
-msgid "[author] marked [link:an issue] as a regression"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:124
-msgid "[author] linked [link:an issue] on [provider]"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:130
-msgid "[author] saw [link:a new issue]"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:137
-msgid "[author] assigned [link:an issue] to themselves"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:151
-msgid "[author] assigned [link:an issue] to [assignee]"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:157
-msgid "[author] assigned [link:an issue] to an [help:unknown user]"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:163
-msgid "[author] unassigned [link:an issue]"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:168
-msgid "[author] merged [count] [link:issues]"
-msgstr ""
-
-#: static/sentry/app/components/activity/item.jsx:174
-msgid "[author] released version [version]"
-msgstr ""
-
-#: static/sentry/app/components/issues/snoozeAction.jsx:49
-msgid "zZz"
-msgstr ""
-
-#: static/sentry/app/components/issues/snoozeAction.jsx:54
-msgid "How long should we ignore this issue?"
-msgstr ""
-
-#: static/sentry/app/components/issues/snoozeAction.jsx:57
-msgid "2 hours"
-msgstr ""
-
-#: static/sentry/app/components/issues/snoozeAction.jsx:60
-msgid "Forever"
-msgstr ""
-
-#: static/sentry/app/components/groupListHeader.jsx:13
-msgid "Last 24 hours"
-msgstr ""
-
-#: static/sentry/app/components/groupListHeader.jsx:15
-msgid "events"
-msgstr ""
-
-#: static/sentry/app/components/assigneeSelector.jsx:201
-msgid "No matching users found."
-msgstr ""
-
-#: static/sentry/app/components/assigneeSelector.jsx:228
-msgid "Filter people"
-msgstr ""
-
-#: static/sentry/app/components/assigneeSelector.jsx:237
-msgid "Clear Assignee"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:37
-msgid "Delete event.."
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:134
-msgid ""
-"This event is resolved due to the Auto Resolve configuration for this "
-"project"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:139
-msgid "Unresolve"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:162
-msgid "Resolved in next release"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:168
-msgid "Snooze notifications until this issue reoccurs in a future release."
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:166
-msgid "Set up release tracking in order to use this feature."
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:167
-msgid "Resolved in next release."
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:179
-msgid "Remove Ignored Status"
-msgstr ""
-
-#: static/sentry/app/components/customSnoozeModal.jsx:105
-msgid "Ignore"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:192
-msgid "for 30 minutes"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:195
-msgid "for 2 hours"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:198
-msgid "for 24 hours"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:201
-msgid "for 1 week"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:204
-msgid "until custom date..."
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:207
-msgid "forever"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:222
-msgid "Delete"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:223
-msgid "Deleting this event is permanent. Are you sure you wish to continue?"
-msgstr ""
-
-#: static/sentry/app/views/groupDetails/actions.jsx:260
-msgid "Link Issue Tracker"
-msgstr ""
-
-#: static/sentry/app/views/stream/savedSearchSelector.jsx:209
-msgid "Save Current Search"
-msgstr ""
-
-#: static/sentry/app/views/stream/savedSearchSelector.jsx:117
-msgid ""
-"Saving this search will give you and your team quick access to it in the "
-"future."
-msgstr ""
-
-#: static/sentry/app/views/stream/savedSearchSelector.jsx:135
-msgid "Make this the default view for myself."
-msgstr ""
-
-#: static/sentry/app/views/stream/savedSearchSelector.jsx:140
-msgid "Make this the default view for my team."
-msgstr ""
-
-#: static/sentry/app/views/stream/savedSearchSelector.jsx:148
-msgid "Save"
-msgstr ""
-
-#: static/sentry/app/views/stream/savedSearchSelector.jsx:175
-msgid "Custom Search"
-msgstr ""
-
-#: static/sentry/app/views/stream/savedSearchSelector.jsx:197
-msgid "There don't seem to be any saved searches yet."
-msgstr ""
-
-#: static/sentry/app/views/stream/sortOptions.jsx:59
-msgid "Sort by"
-msgstr ""
-
-#: static/sentry/app/views/stream/actionLink.jsx:114
-msgid "This action cannot be undone."
-msgstr ""
-
-#: static/sentry/app/views/stream/searchBar.jsx:68
-msgid "Tag"
-msgstr ""
-
-#: static/sentry/app/views/stream/searchBar.jsx:69
-msgid "key/value pair associated to an issue"
-msgstr ""
-
-#: static/sentry/app/views/stream/searchBar.jsx:76
-msgid "State of an issue"
-msgstr ""
-
-#: static/sentry/app/views/stream/searchBar.jsx:83
-msgid "team member assigned to an issue"
-msgstr ""
-
-#: static/sentry/app/views/stream/searchBar.jsx:89
-msgid "Bookmarked By"
-msgstr ""
-
-#: static/sentry/app/views/stream/searchBar.jsx:90
-msgid "team member who bookmarked an issue"
-msgstr ""
-
-#: static/sentry/app/views/stream/searchBar.jsx:96
-msgid "or paste an event id to jump straight to it"
-msgstr ""
-
-#: static/sentry/app/components/activity/note.jsx:35
-msgid "Are you sure you wish to delete this comment?"
-msgstr ""
-
-#: static/sentry/app/components/clippedBox.jsx:68
-msgid "Show more"
-msgstr ""
-
-#: static/sentry/app/components/events/errorItem.jsx:29
-msgid "Collapse"
-msgstr ""
-
-#: static/sentry/app/components/events/errorItem.jsx:29
-msgid "Expand"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/richHttpContent.jsx:72
-msgid "Query String"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/richHttpContent.jsx:83
-msgid "Body"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/richHttpContent.jsx:89
-msgid "Cookies"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/richHttpContent.jsx:94
-msgid "Headers"
-msgstr ""
-
-#: static/sentry/app/components/group/seenInfo.jsx:71
-msgid "When"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/crashHeader.jsx:49
-msgid "Original"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/crashHeader.jsx:51
-msgid "Symbolicated"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/crashHeader.jsx:57
-msgid "Minified"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/crashHeader.jsx:59
-msgid "Unsymbolicated"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/crashHeader.jsx:94
-msgid "Exception"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/crashHeader.jsx:96
-msgid "Toggle stacktrace order"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/crashHeader.jsx:98
-msgid "most recent call first"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/crashHeader.jsx:100
-msgid "most recent call last"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/crashHeader.jsx:107
-msgid "App Only"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/crashHeader.jsx:109
-msgid "Full"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/frame.jsx:85
-msgid "Source Map"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/frame.jsx:235
-msgid "No additional details are available for this frame."
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/frame.jsx:249
-msgid "Toggle context"
-msgstr ""
-
-#: static/sentry/app/components/customSnoozeModal.jsx:65
-msgid "Ignore until:"
-msgstr ""
-
-#: static/sentry/app/components/customSnoozeModal.jsx:71
-msgid "Date:"
-msgstr ""
-
-#: static/sentry/app/components/customSnoozeModal.jsx:84
-msgid "Time (UTC):"
-msgstr ""
-
-#: static/sentry/app/components/customSnoozeModal.jsx:99
-msgid "Please enter a valid date in the future"
-msgstr ""
-
-#: static/sentry/app/components/group/issuePluginActions.jsx:41
-msgid "Create New Issue"
-msgstr ""
-
-#: static/sentry/app/components/group/issuePluginActions.jsx:42
-msgid "Link with Existing Issue"
-msgstr ""
-
-#: static/sentry/app/components/events/interfaces/stacktraceContent.jsx:34
-#, python-format
-msgid "Frames %d until %d were omitted and not available."
-msgstr ""
diff --git a/src/sentry/locale/af/LC_MESSAGES/django.po b/src/sentry/locale/af/LC_MESSAGES/django.po
index 9edf6797bf9ee1..a55b083f2d5ce6 100644
--- a/src/sentry/locale/af/LC_MESSAGES/django.po
+++ b/src/sentry/locale/af/LC_MESSAGES/django.po
@@ -1,5 +1,5 @@
# SOME DESCRIPTIVE TITLE.
-# Copyright (C) 2015 THE PACKAGE'S COPYRIGHT HOLDER
+# Copyright (C) 2018 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
@@ -8,94 +8,131 @@ msgid ""
msgstr ""
"Project-Id-Version: sentry\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2016-12-13 23:17+0000\n"
-"PO-Revision-Date: 2016-12-13 23:18+0000\n"
-"Last-Translator: mattrobenolt \n"
+"POT-Creation-Date: 2018-01-26 01:15+0000\n"
+"PO-Revision-Date: 2018-01-26 01:16+0000\n"
+"Last-Translator: David Cramer \n"
"Language-Team: Afrikaans (http://www.transifex.com/getsentry/sentry/language/af/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.3.4\n"
+"Generated-By: Babel 2.5.3\n"
"Language: af\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: admin.py:165
+#: admin.py:168 admin.py:183
+#: static/sentry/app/views/inviteMember/inviteMember.jsx:228
+#: templates/sentry/admin/status/mail.html:25 web/forms/accounts.py:311
+msgid "Username"
+msgstr ""
+
+#: admin.py:171 admin.py:186
+msgid "Required. 128 characters or fewer. Letters, digits and @/./+/-/_ only."
+msgstr ""
+
+#: admin.py:175 admin.py:190
+msgid "This value may contain only letters, numbers and @/./+/-/_ characters."
+msgstr ""
+
+#: admin.py:202
msgid "Personal info"
msgstr ""
-#: admin.py:166
+#: admin.py:204
msgid "Permissions"
msgstr ""
-#: admin.py:167
+#: admin.py:206
msgid "Important dates"
msgstr ""
-#: admin.py:255
+#: admin.py:297
msgid "Password changed successfully."
msgstr ""
-#: admin.py:265
+#: admin.py:307
#, python-format
msgid "Change password: %s"
msgstr ""
-#: constants.py:38 static/sentry/app/views/stream/sortOptions.jsx:47
+#: constants.py:44 static/sentry/app/views/stream/sortOptions.jsx:50
msgid "Priority"
msgstr "Prioriteit"
-#: constants.py:39 constants.py:46
-#: static/sentry/app/views/stream/sortOptions.jsx:52
+#: constants.py:45 constants.py:52
+#: static/sentry/app/views/groupTagValues.jsx:159
+#: static/sentry/app/views/stream/sortOptions.jsx:55
msgid "Last Seen"
msgstr "Laas Gesien"
-#: constants.py:40 constants.py:47
-#: static/sentry/app/views/stream/sortOptions.jsx:45
+#: constants.py:46 constants.py:52
+#: static/sentry/app/views/stream/sortOptions.jsx:48
msgid "First Seen"
msgstr "Eerste Gesien"
-#: constants.py:41 static/sentry/app/views/stream/sortOptions.jsx:49
+#: constants.py:47 static/sentry/app/views/stream/sortOptions.jsx:52
msgid "Frequency"
msgstr "Frekwensie"
-#: constants.py:45
+#: constants.py:52
msgid "Score"
msgstr "Telling"
-#: constants.py:199
+#: constants.py:197
#, python-brace-format
msgid "The {name} integration has been enabled."
msgstr ""
-#: constants.py:201
+#: constants.py:199
#, python-brace-format
msgid "The {name} integration has been disabled."
msgstr ""
-#: constants.py:203
+#: constants.py:201
#, python-brace-format
msgid "Configuration for the {name} integration has been saved."
msgstr ""
-#: auth/helper.py:30
+#: api/endpoints/organization_auth_provider_details.py:12
+#: api/endpoints/organization_auth_provider_send_reminders.py:12
+#: web/frontend/organization_auth_settings.py:22
+msgid "The SSO feature is not enabled for this organization."
+msgstr ""
+
+#: api/endpoints/user_appearance.py:36 web/forms/accounts.py:464
+msgid "Default (let Sentry decide)"
+msgstr ""
+
+#: api/endpoints/user_appearance.py:37 web/forms/accounts.py:464
+msgid "Most recent call last"
+msgstr ""
+
+#: api/endpoints/user_appearance.py:38 web/forms/accounts.py:465
+msgid "Most recent call first"
+msgstr ""
+
+#: auth/helper.py:34
msgid "You have successfully linked your account to your SSO provider."
msgstr ""
-#: auth/helper.py:32
+#: auth/helper.py:37
msgid ""
"SSO has been configured for your organization and any existing members have "
"been sent an email to link their accounts."
msgstr ""
-#: auth/helper.py:34
+#: auth/helper.py:40
msgid "There was an error encountered during authentication."
msgstr ""
-#: auth/helper.py:36
+#: auth/helper.py:42
msgid "You must be authenticated to link accounts."
msgstr ""
-#: auth/password_validation.py:91
+#: auth/helper.py:44
+msgid "The provider did not return a valid user identity."
+msgstr ""
+
+#: auth/password_validation.py:94
#, python-format
msgid ""
"This password is too short. It must contain at least %(min_length)d "
@@ -106,310 +143,338 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: auth/password_validation.py:101
+#: auth/password_validation.py:104
#, python-format
msgid "Your password must contain at least %(min_length)d character."
msgid_plural "Your password must contain at least %(min_length)d characters."
msgstr[0] ""
msgstr[1] ""
-#: auth/password_validation.py:114
+#: auth/password_validation.py:123
+#, python-format
+msgid ""
+"This password is too long. It must contain no more than %(max_length)d "
+"character."
+msgid_plural ""
+"This password is too long. It must contain no more than %(max_length)d "
+"characters."
+msgstr[0] ""
+msgstr[1] ""
+
+#: auth/password_validation.py:133
+#, python-format
+msgid "Your password must contain no more than %(max_length)d character."
+msgid_plural ""
+"Your password must contain no more than %(max_length)d characters."
+msgstr[0] ""
+msgstr[1] ""
+
+#: auth/password_validation.py:148
msgid "This password is entirely numeric."
msgstr ""
-#: auth/password_validation.py:119
+#: auth/password_validation.py:153
msgid "Your password can't be entirely numeric."
msgstr ""
-#: conf/server.py:96
+#: auth/providers/saml2.py:35
+msgid "The organization does not exist or does not have SAML SSO enabled."
+msgstr ""
+
+#: auth/providers/saml2.py:36
+#, python-brace-format
+msgid "SAML SSO failed, {reason}"
+msgstr ""
+
+#: conf/server.py:137
msgid "Afrikaans"
msgstr ""
-#: conf/server.py:97
+#: conf/server.py:138
msgid "Arabic"
msgstr ""
-#: conf/server.py:98
+#: conf/server.py:138
msgid "Azerbaijani"
msgstr ""
-#: conf/server.py:99
+#: conf/server.py:140
msgid "Bulgarian"
msgstr ""
-#: conf/server.py:100
+#: conf/server.py:141
msgid "Belarusian"
msgstr ""
-#: conf/server.py:101
+#: conf/server.py:141
msgid "Bengali"
msgstr ""
-#: conf/server.py:102
+#: conf/server.py:142
msgid "Breton"
msgstr ""
-#: conf/server.py:103
+#: conf/server.py:143
msgid "Bosnian"
msgstr ""
-#: conf/server.py:104
+#: conf/server.py:143
msgid "Catalan"
msgstr ""
-#: conf/server.py:105
+#: conf/server.py:144
msgid "Czech"
msgstr ""
-#: conf/server.py:106
+#: conf/server.py:144
msgid "Welsh"
msgstr ""
-#: conf/server.py:107
+#: conf/server.py:144
msgid "Danish"
msgstr ""
-#: conf/server.py:108
+#: conf/server.py:145
msgid "German"
msgstr ""
-#: conf/server.py:109
+#: conf/server.py:145
msgid "Greek"
msgstr ""
-#: conf/server.py:110
+#: conf/server.py:145
msgid "English"
msgstr ""
-#: conf/server.py:111
+#: conf/server.py:146
msgid "Esperanto"
msgstr ""
-#: conf/server.py:112
+#: conf/server.py:147
msgid "Spanish"
msgstr ""
-#: conf/server.py:113
+#: conf/server.py:147
msgid "Estonian"
msgstr ""
-#: conf/server.py:114
+#: conf/server.py:148
msgid "Basque"
msgstr ""
-#: conf/server.py:115
+#: conf/server.py:149
msgid "Persian"
msgstr ""
-#: conf/server.py:116
+#: conf/server.py:149
msgid "Finnish"
msgstr ""
-#: conf/server.py:117
+#: conf/server.py:150
msgid "French"
msgstr ""
-#: conf/server.py:118
+#: conf/server.py:150
msgid "Irish"
msgstr ""
-#: conf/server.py:119
+#: conf/server.py:150
msgid "Galician"
msgstr ""
-#: conf/server.py:120
+#: conf/server.py:151
msgid "Hebrew"
msgstr ""
-#: conf/server.py:121
+#: conf/server.py:151
msgid "Hindi"
msgstr ""
-#: conf/server.py:122
+#: conf/server.py:151
msgid "Croatian"
msgstr ""
-#: conf/server.py:123
+#: conf/server.py:153
msgid "Hungarian"
msgstr ""
-#: conf/server.py:124
+#: conf/server.py:154
msgid "Interlingua"
msgstr ""
-#: conf/server.py:125
+#: conf/server.py:154
msgid "Indonesian"
msgstr ""
-#: conf/server.py:126
+#: conf/server.py:155
msgid "Icelandic"
msgstr ""
-#: conf/server.py:127
+#: conf/server.py:156
msgid "Italian"
msgstr ""
-#: conf/server.py:128
+#: conf/server.py:156
msgid "Japanese"
msgstr ""
-#: conf/server.py:129
+#: conf/server.py:157
msgid "Georgian"
msgstr ""
-#: conf/server.py:130
+#: conf/server.py:157
msgid "Kazakh"
msgstr ""
-#: conf/server.py:131
+#: conf/server.py:157
msgid "Khmer"
msgstr ""
-#: conf/server.py:132
+#: conf/server.py:159
msgid "Kannada"
msgstr ""
-#: conf/server.py:133
+#: conf/server.py:160
msgid "Korean"
msgstr ""
-#: conf/server.py:134
+#: conf/server.py:160
msgid "Luxembourgish"
msgstr ""
-#: conf/server.py:135
+#: conf/server.py:162
msgid "Lithuanian"
msgstr ""
-#: conf/server.py:136
+#: conf/server.py:163
msgid "Latvian"
msgstr ""
-#: conf/server.py:137
+#: conf/server.py:163
msgid "Macedonian"
msgstr ""
-#: conf/server.py:138
+#: conf/server.py:164
msgid "Malayalam"
msgstr ""
-#: conf/server.py:139
+#: conf/server.py:165
msgid "Mongolian"
msgstr ""
-#: conf/server.py:140
+#: conf/server.py:165
msgid "Burmese"
msgstr ""
-#: conf/server.py:141
+#: conf/server.py:166
msgid "Norwegian Bokmal"
msgstr ""
-#: conf/server.py:142
+#: conf/server.py:166
msgid "Nepali"
msgstr ""
-#: conf/server.py:143
+#: conf/server.py:167
msgid "Dutch"
msgstr ""
-#: conf/server.py:144
+#: conf/server.py:167
msgid "Norwegian Nynorsk"
msgstr ""
-#: conf/server.py:145
+#: conf/server.py:168
msgid "Ossetic"
msgstr ""
-#: conf/server.py:146
+#: conf/server.py:169
msgid "Punjabi"
msgstr ""
-#: conf/server.py:147
+#: conf/server.py:169
msgid "Polish"
msgstr ""
-#: conf/server.py:148
+#: conf/server.py:170
msgid "Portuguese"
msgstr ""
-#: conf/server.py:149
+#: conf/server.py:170
msgid "Brazilian Portuguese"
msgstr ""
-#: conf/server.py:150
+#: conf/server.py:171
msgid "Romanian"
msgstr ""
-#: conf/server.py:151
+#: conf/server.py:171
msgid "Russian"
msgstr ""
-#: conf/server.py:152
+#: conf/server.py:172
msgid "Slovak"
msgstr ""
-#: conf/server.py:153
+#: conf/server.py:173
msgid "Slovenian"
msgstr ""
-#: conf/server.py:154
+#: conf/server.py:173
msgid "Albanian"
msgstr ""
-#: conf/server.py:155
+#: conf/server.py:174
msgid "Serbian"
msgstr ""
-#: conf/server.py:156
+#: conf/server.py:175
msgid "Swedish"
msgstr ""
-#: conf/server.py:157
+#: conf/server.py:175
msgid "Swahili"
msgstr ""
-#: conf/server.py:158
+#: conf/server.py:176
msgid "Tamil"
msgstr ""
-#: conf/server.py:159
+#: conf/server.py:176
msgid "Telugu"
msgstr ""
-#: conf/server.py:160
+#: conf/server.py:176
msgid "Thai"
msgstr ""
-#: conf/server.py:161
+#: conf/server.py:177
msgid "Turkish"
msgstr ""
-#: conf/server.py:162
+#: conf/server.py:177
msgid "Tatar"
msgstr ""
-#: conf/server.py:163
+#: conf/server.py:177
msgid "Udmurt"
msgstr ""
-#: conf/server.py:164
+#: conf/server.py:178
msgid "Ukrainian"
msgstr ""
-#: conf/server.py:165
+#: conf/server.py:179
msgid "Urdu"
msgstr ""
-#: conf/server.py:166
+#: conf/server.py:179
msgid "Vietnamese"
msgstr ""
-#: conf/server.py:167
+#: conf/server.py:180
msgid "Simplified Chinese"
msgstr ""
-#: conf/server.py:168
+#: conf/server.py:180
msgid "Traditional Chinese"
msgstr ""
@@ -417,153 +482,178 @@ msgstr ""
msgid "Big Integer"
msgstr ""
-#: debug/panels/base.py:41
+#: debug/panels/base.py:42
#, python-format
msgid "%(calls)d call in %(duration).2fms"
msgid_plural "%(calls)d calls in %(duration).2fms"
msgstr[0] ""
msgstr[1] ""
-#: debug/panels/redis.py:79
+#: debug/panels/redis.py:81
msgid "Redis"
msgstr ""
-#: interfaces/http.py:214
+#: identity/pipeline.py:16
+#, python-brace-format
+msgid ""
+"Your {identity_provider} account has been associated with your Sentry "
+"account"
+msgstr ""
+
+#: interfaces/http.py:241
msgid "Request"
msgstr "Versoek"
-#: interfaces/stacktrace.py:764
+#: interfaces/stacktrace.py:770
msgid "Stacktrace (most recent call first):"
msgstr ""
-#: interfaces/stacktrace.py:766
+#: interfaces/stacktrace.py:772
msgid "Stacktrace (most recent call last):"
msgstr ""
-#: models/apikey.py:55 models/project.py:85 models/projectkey.py:54
-#: models/team.py:107
+#: models/apiapplication.py:38 models/apikey.py:53 models/project.py:106
+#: models/projectkey.py:58 models/team.py:119
msgid "Active"
msgstr ""
-#: models/apikey.py:56 models/projectkey.py:55
+#: models/apiapplication.py:39 models/apikey.py:53 models/projectkey.py:59
msgid "Inactive"
msgstr ""
-#: models/authenticator.py:170
+#: models/authenticator.py:176
msgid "Enroll"
msgstr ""
-#: models/authenticator.py:171
+#: models/authenticator.py:177
msgid "Info"
msgstr ""
-#: models/authenticator.py:172
-#: static/sentry/app/components/activity/note.jsx:36
-#: templates/sentry/organization-members.html:127
-#: templates/sentry/organization-members.html:132
+#: models/authenticator.py:178
+#: static/sentry/app/components/activity/note.jsx:41
+#: static/sentry/app/views/projectSavedSearches.jsx:130
+#: static/sentry/app/views/settings/organization/members/organizationMemberRow.jsx:187
+#: static/sentry/app/views/settings/organization/members/organizationMemberRow.jsx:199
msgid "Remove"
msgstr ""
-#: models/authenticator.py:272
+#: models/authenticator.py:278
msgid "Recovery Codes"
msgstr ""
-#: models/authenticator.py:273
+#: models/authenticator.py:280
msgid ""
"Recovery codes can be used to access your account in the event you lose "
"access to your device and cannot receive two-factor authentication codes."
msgstr ""
-#: models/authenticator.py:276
+#: models/authenticator.py:284
msgid "Activate"
msgstr ""
-#: models/authenticator.py:277
+#: models/authenticator.py:285
msgid "View Codes"
msgstr ""
-#: models/authenticator.py:392
+#: models/authenticator.py:396
msgid "Authenticator App"
msgstr ""
-#: models/authenticator.py:393
+#: models/authenticator.py:398
msgid ""
"An authenticator application that supports TOTP (like Google Authenticator "
"or 1Password) can be used to conveniently secure your account. A new token "
"is generated every 30 seconds."
msgstr ""
-#: models/authenticator.py:408
+#: models/authenticator.py:413
msgid "Text Message"
msgstr ""
-#: models/authenticator.py:409
+#: models/authenticator.py:415
msgid ""
"This authenticator sends you text messages for verification. It's useful as"
" a backup method or when you do not have a phone that supports an "
"authenticator application."
msgstr ""
-#: models/authenticator.py:440
+#: models/authenticator.py:446
#, python-format
msgid ""
"A confirmation code was sent to your phone. It is valid for %d seconds."
msgstr ""
-#: models/authenticator.py:443
+#: models/authenticator.py:451
msgid ""
"Error: we failed to send a text message to you. You can try again later or "
"sign in with a different method."
msgstr ""
-#: models/authenticator.py:451
+#: models/authenticator.py:462
#, python-format
msgid ""
"%(code)s is your Sentry two-factor enrollment code. You are about to set up "
"text message based two-factor authentication."
msgstr ""
-#: models/authenticator.py:455
+#: models/authenticator.py:467
#, python-format
msgid "%(code)s is your Sentry authentication code."
msgstr ""
-#: models/authenticator.py:458
+#: models/authenticator.py:470
#, python-format
msgid "Requested from %(ip)s"
msgstr ""
-#: models/authenticator.py:468
+#: models/authenticator.py:480
msgid "Configure"
msgstr ""
-#: models/authenticator.py:469
+#: models/authenticator.py:481
msgid "U2F (Universal 2nd Factor)"
msgstr ""
-#: models/authenticator.py:470
+#: models/authenticator.py:483
msgid ""
"Authenticate with a U2F hardware device. This is a device like a Yubikey or "
"something similar which supports FIDO's U2F specification. This also "
"requires a browser which supports this system (like Google Chrome)."
msgstr ""
-#: models/authenticator.py:560
+#: models/authenticator.py:575
msgid "created at"
msgstr ""
-#: models/authenticator.py:561
+#: models/authenticator.py:576
msgid "last used at"
msgstr ""
-#: models/authenticator.py:573
+#: models/authenticator.py:588
msgid "authenticator"
msgstr ""
-#: models/authenticator.py:574
+#: models/authenticator.py:589
msgid "authenticators"
msgstr ""
+#: models/dsymfile.py:142
+#: static/sentry/app/views/projectInstall/platform.jsx:118
+msgid "Generic"
+msgstr ""
+
+#: models/dsymfile.py:142
+msgid "Apple"
+msgstr ""
+
+#: models/dsymfile.py:143
+msgid "Android"
+msgstr ""
+
+#: models/email.py:18 models/user.py:38 models/useremail.py:20
+msgid "email address"
+msgstr ""
+
#: models/event.py:53
msgid "message"
msgstr "boodskap"
@@ -572,129 +662,150 @@ msgstr "boodskap"
msgid "messages"
msgstr "boodskappe"
-#: models/event.py:123 models/group.py:415
+#: models/event.py:123 models/group.py:395
msgid "error"
msgstr "fout"
-#: models/group.py:168
-#: static/sentry/app/components/organizationIssueList.jsx:49
+#: models/group.py:197
+#: static/sentry/app/components/organizationIssueList.jsx:53
+#: static/sentry/app/views/projectUserReports.jsx:214
msgid "Unresolved"
msgstr ""
-#: models/group.py:169 models/groupresolution.py:37
+#: models/group.py:197 models/groupresolution.py:35
msgid "Resolved"
msgstr ""
-#: models/group.py:170
+#: models/group.py:198
msgid "Ignored"
msgstr ""
-#: models/group.py:192
+#: models/group.py:222
msgid "grouped messages"
msgstr "gegroepeerde boodskappe"
-#: models/group.py:193
+#: models/group.py:223
msgid "grouped message"
msgstr "gegroepeerde boodskap"
-#: models/groupresolution.py:36
-msgid "Pending"
+#: models/grouphash.py:31
+msgid "Locked (Migration in Progress)"
msgstr ""
-#: models/organization.py:80 models/tagkey.py:67
-msgid "Visible"
+#: models/grouplink.py:39
+msgid "Commit"
msgstr ""
-#: models/organization.py:81 models/project.py:86 models/tagkey.py:68
-#: models/team.py:108
+#: models/grouplink.py:40
+msgid "Pull Request"
+msgstr ""
+
+#: models/grouplink.py:41
+msgid "Tracker Issue"
+msgstr ""
+
+#: models/grouplink.py:46
+msgid "Resolves"
+msgstr ""
+
+#: models/grouplink.py:47
+msgid "Linked"
+msgstr ""
+
+#: models/groupresolution.py:35
+msgid "Pending"
+msgstr ""
+
+#: models/project.py:106 models/team.py:119
+#: tagstore/legacy/models/tagkey.py:35 tagstore/v2/models/tagkey.py:35
msgid "Pending Deletion"
msgstr ""
-#: models/organization.py:82 models/project.py:87 models/tagkey.py:69
-#: models/team.py:109
+#: models/project.py:107 models/team.py:120
+#: tagstore/legacy/models/tagkey.py:36 tagstore/v2/models/tagkey.py:36
msgid "Deletion in Progress"
msgstr ""
-#: models/user.py:33
+#: models/user.py:34
msgid "username"
msgstr ""
-#: models/user.py:36
+#: models/user.py:37
msgid "name"
msgstr ""
-#: models/user.py:38 models/useremail.py:22
-msgid "email address"
-msgstr ""
-
#: models/user.py:40
msgid "staff status"
msgstr ""
-#: models/user.py:41
+#: models/user.py:42
msgid "Designates whether the user can log into this admin site."
msgstr ""
-#: models/user.py:44
+#: models/user.py:46
msgid "active"
msgstr ""
-#: models/user.py:45
+#: models/user.py:49
msgid ""
"Designates whether this user should be treated as active. Unselect this "
"instead of deleting accounts."
msgstr ""
-#: models/user.py:48
+#: models/user.py:54
msgid "superuser status"
msgstr ""
-#: models/user.py:49
+#: models/user.py:57
msgid ""
"Designates that this user has all permissions without explicitly assigning "
"them."
msgstr ""
-#: models/user.py:52
+#: models/user.py:62
msgid "managed"
msgstr ""
-#: models/user.py:53
+#: models/user.py:65
msgid ""
"Designates whether this user should be treated as managed. Select this to "
"disallow the user from modifying their account (username, password, etc)."
msgstr ""
-#: models/user.py:57
+#: models/user.py:71
msgid "password expired"
msgstr ""
-#: models/user.py:58
+#: models/user.py:74
msgid ""
"If set to true then the user needs to change the password on next sign in."
msgstr ""
-#: models/user.py:61
+#: models/user.py:79
msgid "date of last password change"
msgstr ""
-#: models/user.py:62
+#: models/user.py:81
msgid "The date the password was changed last."
msgstr ""
-#: models/user.py:66
+#: models/user.py:86
msgid "date joined"
msgstr ""
-#: models/user.py:76
+#: models/user.py:87
+msgid "last active"
+msgstr ""
+
+#: models/user.py:97
msgid "user"
msgstr "gebruiker"
-#: models/user.py:77 static/sentry/app/components/groupListHeader.jsx:16
+#: models/user.py:98 static/sentry/app/components/groupListHeader.jsx:21
msgid "users"
msgstr ""
-#: models/useremail.py:27
+#: models/useremail.py:26
msgid "verified"
msgstr ""
@@ -702,19 +813,22 @@ msgstr ""
msgid "Designates whether this user has confirmed their email."
msgstr ""
-#: plugins/base/configuration.py:65 web/frontend/project_plugins.py:24
-#: web/frontend/project_quotas.py:35
+#: plugins/base/configuration.py:97 web/frontend/project_plugins.py:23
msgid "Your settings were saved successfully."
msgstr ""
-#: plugins/sentry_webhooks/plugin.py:33
+#: plugins/sentry_webhooks/plugin.py:32
msgid "Callback URLs"
msgstr ""
-#: plugins/sentry_webhooks/plugin.py:36
+#: plugins/sentry_webhooks/plugin.py:37
msgid "Enter callback URLs to POST new events to (one per line)."
msgstr ""
+#: tagstore/legacy/models/tagkey.py:34 tagstore/v2/models/tagkey.py:34
+msgid "Visible"
+msgstr ""
+
#: templates/sentry/403-csrf-failure.html:5
#: templates/sentry/403-csrf-failure.html:10
msgid "CSRF Verification Failed"
@@ -771,7 +885,7 @@ msgstr ""
msgid "%(org_name)s is using Sentry to aggregate errors."
msgstr ""
-#: templates/sentry/accept-organization-invite.html:30
+#: templates/sentry/accept-organization-invite.html:31
#, python-format
msgid ""
"You have been invited to join this organization, which manages "
@@ -779,46 +893,47 @@ msgid ""
msgstr ""
#: templates/sentry/accept-organization-invite.html:40
+msgid "You have been invited to join this organization."
+msgstr ""
+
+#: templates/sentry/accept-organization-invite.html:44
msgid ""
"To continue, you must either login to your existing account, or create a new"
" one."
msgstr ""
-#: templates/sentry/accept-organization-invite.html:44
+#: templates/sentry/accept-organization-invite.html:48
msgid "Login as an existing user"
msgstr ""
-#: templates/sentry/accept-organization-invite.html:46
+#: templates/sentry/accept-organization-invite.html:50
msgid "Create a new account"
msgstr ""
-#: templates/sentry/accept-organization-invite.html:53
+#: templates/sentry/accept-organization-invite.html:57
#, python-format
msgid "Join the %(org_name)s organization"
msgstr ""
-#: templates/sentry/admin-queue.html:8 templates/sentry/bases/admin.html:14
-msgid "Queue"
-msgstr "Tou"
-
#: templates/sentry/auth-confirm-identity.html:7
#: templates/sentry/auth-confirm-link.html:7
msgid "Confirm Identity"
msgstr ""
-#: templates/sentry/auth-confirm-identity.html:43
+#: templates/sentry/auth-confirm-identity.html:38
#: templates/sentry/auth-link-login.html:7
#: templates/sentry/auth-link-login.html:31 templates/sentry/login.html:7
-#: templates/sentry/login.html:38 templates/sentry/login.html.py:12
-#: templates/sentry/organization-login.html:7
-#: templates/sentry/organization-login.html:32
-#: templates/sentry/organization-login.html:55
+#: templates/sentry/login.html.py:16
+#: templates/sentry/organization-login.html:39
+#: templates/sentry/organization-login.html:65
+#: templates/sentry/organization-login.html:88
msgid "Login"
msgstr ""
-#: templates/sentry/auth-confirm-identity.html:44
-#: templates/sentry/auth-link-login.html:31 templates/sentry/login.html:38
-#: templates/sentry/organization-login.html:55
+#: templates/sentry/account/sudo.html:68
+#: templates/sentry/auth-confirm-identity.html:39
+#: templates/sentry/auth-link-login.html:31 templates/sentry/login.html:42
+#: templates/sentry/organization-login.html:88
msgid "Lost your password?"
msgstr ""
@@ -826,90 +941,6 @@ msgstr ""
msgid "Link Identity"
msgstr ""
-#: templates/sentry/create-organization-member.html:6
-#: templates/sentry/create-organization-member.html:18
-msgid "Add Member to Organization"
-msgstr ""
-
-#: templates/sentry/create-organization-member.html:23
-msgid ""
-"Invite a member to join this organization via their email address. If they "
-"do not already have an account, they will first be asked to create one."
-msgstr ""
-
-#: templates/sentry/create-organization-member.html:25
-msgid "You may add a user by their username if they already have an account."
-msgstr ""
-
-#: templates/sentry/create-organization-member.html:43
-msgid "Add Member"
-msgstr ""
-
-#: static/sentry/app/components/sidebar/organizationSelector.jsx:84
-#: templates/sentry/create-organization.html:6
-msgid "New Organization"
-msgstr ""
-
-#: templates/sentry/create-organization.html:11
-msgid "Create a New Organization"
-msgstr ""
-
-#: templates/sentry/create-organization.html:13
-msgid ""
-"Organizations represent the top level in your hierarchy. You'll be able to "
-"bundle a collection of teams within an organization as well as give "
-"organization-wide permissions to users."
-msgstr ""
-
-#: templates/sentry/create-organization.html:24
-msgid "Create Organization"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeContainer.jsx:37
-#: templates/sentry/create-project.html:8
-#: templates/sentry/projects/cannot_create_teams.html:5
-msgid "New Project"
-msgstr "Nuwe Projek"
-
-#: templates/sentry/create-project.html:13
-msgid "Create a New Project"
-msgstr ""
-
-#: templates/sentry/create-project.html:15
-msgid ""
-"Projects allow you to scope events to a specific application in your "
-"organization. For example, you might have separate projects for production "
-"vs development instances, or separate projects for your web app and mobile "
-"app."
-msgstr ""
-
-#: templates/sentry/create-project.html:20
-#: templates/sentry/organization-api-key-settings.html:19
-#: templates/sentry/organization-member-settings.html:22
-#: templates/sentry/organization-settings.html:17
-#: templates/sentry/partial/form_base.html:5
-msgid "Please correct the errors below."
-msgstr "Korrigeer asseblief die foute hieronder."
-
-#: templates/sentry/create-project.html:31
-msgid "Create Project"
-msgstr ""
-
-#: static/sentry/app/components/organizations/homeContainer.jsx:47
-#: templates/sentry/create-team.html:7
-msgid "New Team"
-msgstr ""
-
-#: templates/sentry/create-team.html:11
-msgid "Create a New Team"
-msgstr ""
-
-#: templates/sentry/create-team.html:13
-msgid ""
-"Teams group members' access to a specific focus, e.g. a major product or "
-"application that may have sub-projects."
-msgstr ""
-
#: templates/sentry/error-page-embed.html:258
msgid "It looks like we're having some internal issues."
msgstr ""
@@ -922,18 +953,24 @@ msgstr ""
msgid "If you'd like to help, tell us what happened below."
msgstr ""
-#: static/sentry/app/views/stream/savedSearchSelector.jsx:121
+#: static/sentry/app/views/apiApplicationDetails.jsx:158
+#: static/sentry/app/views/projectKeyDetails.jsx:296
+#: static/sentry/app/views/projectPluginDetails.jsx:127
+#: static/sentry/app/views/settings/account/accountSubscriptions.jsx:93
+#: static/sentry/app/views/settings/organization/apiKeys/organizationApiKeysList.jsx:81
+#: static/sentry/app/views/settings/organization/general/organizationSettingsForm.old.jsx:168
+#: static/sentry/app/views/settings/team/teamSettings.old.jsx:38
+#: static/sentry/app/views/stream/savedSearchSelector.jsx:143
#: templates/sentry/error-page-embed.html:265 web/forms/__init__.py:19
-#: web/forms/accounts.py:273 web/forms/add_project.py:15
-#: web/forms/add_team.py:13
+#: web/forms/accounts.py:180 web/forms/accounts.py:310
msgid "Name"
msgstr "Naam"
-#: templates/sentry/error-page-embed.html:269
-#: templates/sentry/organization-member-details.html:32
-#: templates/sentry/organization-member-settings.html:36
-#: templates/sentry/projects/manage.html:51 web/forms/accounts.py:161
-#: web/forms/accounts.py:275 web/forms/accounts.py:483
+#: static/sentry/app/views/inviteMember/inviteMember.jsx:228
+#: static/sentry/app/views/projectGeneralSettings.jsx:236
+#: static/sentry/app/views/settings/organization/members/organizationMemberDetail.jsx:155
+#: templates/sentry/error-page-embed.html:269 web/forms/accounts.py:186
+#: web/forms/accounts.py:312 web/forms/accounts.py:598
msgid "Email"
msgstr ""
@@ -945,7 +982,7 @@ msgstr ""
msgid "Submit Crash Report"
msgstr ""
-#: static/sentry/app/components/alertMessage.jsx:35
+#: static/sentry/app/components/alertMessage.jsx:33
#: templates/sentry/error-page-embed.html:279
msgid "Close"
msgstr ""
@@ -954,33 +991,47 @@ msgstr ""
msgid "Crash reports powered by enabled."
msgstr ""
+#: static/sentry/app/views/projectKeys.jsx:121
#: templates/sentry/account/security.html:18
-#: templates/sentry/projects/keys.html:35
msgid "Enable"
msgstr ""
@@ -1426,15 +1400,17 @@ msgstr ""
msgid "Two-factor authentication is currently disabled."
msgstr ""
-#: templates/sentry/account/settings.html:14
+#: templates/sentry/account/settings.html:16
+#: templates/sentry/account/subscriptions.html:13
msgid "Your email address has not been verified. "
msgstr ""
-#: templates/sentry/account/settings.html:18
+#: templates/sentry/account/settings.html:20
+#: templates/sentry/account/subscriptions.html:17
msgid "Resend Verification Email."
msgstr ""
-#: templates/sentry/account/settings.html:59
+#: templates/sentry/account/settings.html:56
msgid "Optional"
msgstr ""
@@ -1442,20 +1418,33 @@ msgstr ""
msgid "Verification"
msgstr ""
+#: static/sentry/app/views/accountLayout.jsx:52
+#: static/sentry/app/views/settings/account/navigationConfiguration.jsx:27
+#: templates/sentry/account/subscriptions.html:8
+#: templates/sentry/bases/account.html:44
+msgid "Subscriptions"
+msgstr ""
+
#: templates/sentry/account/sudo.html:7
msgid "Confirm Password"
msgstr ""
+#: static/sentry/app/views/accountLayout.jsx:17
#: templates/sentry/account/sudo.html:33
#: templates/sentry/bases/account.html:20
msgid "Back to organization"
msgstr ""
-#: templates/sentry/account/sudo.html:38
-msgid "Help us keep your account safe by confirming your password."
+#: templates/sentry/account/sudo.html:37
+msgid "Confirm your Identity"
+msgstr ""
+
+#: static/sentry/app/components/modals/sudoModal.jsx:105
+#: templates/sentry/account/sudo.html:40
+msgid "Help us keep your account safe by confirming your identity."
msgstr ""
-#: templates/sentry/account/sudo.html:44
+#: templates/sentry/account/sudo.html:46
msgid "Your password was not valid."
msgstr ""
@@ -1519,9 +1508,27 @@ msgstr ""
msgid "Send Email"
msgstr ""
+#: templates/sentry/account/set_password/confirm.html:6
+#: templates/sentry/account/set_password/confirm.html:9
+#: templates/sentry/account/set_password/confirm.html:21
+#: templates/sentry/account/set_password/failure.html:6
+#: templates/sentry/account/set_password/failure.html:9
+msgid "Set Password"
+msgstr ""
+
+#: templates/sentry/account/set_password/confirm.html:11
+msgid "Set your account password below."
+msgstr ""
+
+#: templates/sentry/account/set_password/failure.html:10
+msgid ""
+"This password link has expired. Request a new password recovery code to set\n"
+"\tyour account password"
+msgstr ""
+
#: templates/sentry/account/twofactor/configure.html:22
#: templates/sentry/account/twofactor/configure.html:24
-#: templatetags/sentry_helpers.py:190
+#: templatetags/sentry_helpers.py:195
msgid "never"
msgstr "nooit"
@@ -1539,7 +1546,6 @@ msgstr ""
msgid "Send Confirmation Code"
msgstr ""
-#: static/sentry/app/components/linkWithConfirmation.jsx:55
#: templates/sentry/account/twofactor/enroll_sms.html:41
#: templates/sentry/account/twofactor/enroll_totp.html:42
#: templates/sentry/admin/users/remove.html:19
@@ -1564,7 +1570,7 @@ msgstr ""
msgid "Server Status"
msgstr "Bediener Status"
-#: static/sentry/app/components/events/interfaces/richHttpContent.jsx:99
+#: static/sentry/app/components/events/interfaces/richHttpContent.jsx:78
#: templates/sentry/admin/status/env.html:12
#: templates/sentry/bases/admin.html:15
msgid "Environment"
@@ -1590,9 +1596,10 @@ msgstr ""
msgid "Environment not found (are you using the builtin Sentry webserver?)."
msgstr "Omgewing nie gevind nie (gebruik jy die ingeboude Sentry webbediener?)."
-#: static/sentry/app/views/projectSettings/index.jsx:85
+#: static/sentry/app/views/projectSettings/index.jsx:95
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:10
#: templates/sentry/admin/status/env.html:40
-#: templates/sentry/projects/manage.html:181
+#: templates/sentry/projects/manage.html:32
msgid "Configuration"
msgstr "Konfigurasie"
@@ -1612,16 +1619,13 @@ msgstr ""
msgid "Host"
msgstr ""
-#: templates/sentry/admin/status/mail.html:25 web/forms/accounts.py:274
-msgid "Username"
-msgstr ""
-
#: templates/sentry/admin/status/mail.html:27
#: templates/sentry/admin/status/mail.html:32
msgid "not set"
msgstr ""
-#: templates/sentry/admin/status/mail.html:30 web/forms/accounts.py:53
+#: static/sentry/app/components/modals/sudoModal.jsx:110
+#: templates/sentry/admin/status/mail.html:30 web/forms/accounts.py:56
msgid "Password"
msgstr ""
@@ -1679,7 +1683,8 @@ msgstr "Verwyder Gebruiker"
msgid "Cannot remove yourself"
msgstr "Kan nie jouself verwyder nie"
-#: static/sentry/app/views/adminProjects.jsx:36
+#: static/sentry/app/views/adminProjects.jsx:37
+#: static/sentry/app/views/settings/organization/projects/organizationProjectsView.jsx:30
#: templates/sentry/admin/users/edit.html:30
#: templates/sentry/bases/admin.html:25
msgid "Projects"
@@ -1693,41 +1698,68 @@ msgstr "Daaglikse Gevalle"
msgid "New User"
msgstr "Nuwe Gebruiker"
-#: static/sentry/app/components/sidebar/organizationSelector.jsx:72
+#: static/sentry/app/components/organizations/homeSidebar.jsx:80
+#: static/sentry/app/components/projectHeader/index.jsx:81
+#: static/sentry/app/components/sidebar/organizationSelector.jsx:103
+#: static/sentry/app/views/adminSettings.jsx:46
+#: static/sentry/app/views/projectAlertRules.jsx:243
+#: static/sentry/app/views/projectAlertSettings.jsx:202
+#: static/sentry/app/views/projectCspSettings.jsx:288
+#: static/sentry/app/views/projectProcessingIssues.jsx:378
+#: static/sentry/app/views/projectUserReportSettings.jsx:343
+#: static/sentry/app/views/teamDetails.jsx:105
#: templates/sentry/bases/account.html:7 templates/sentry/bases/admin.html:18
-#: templates/sentry/bases/organization.html:82
+#: templates/sentry/bases/organization.html:80
msgid "Settings"
msgstr ""
-#: static/sentry/app/components/sidebar/userNav.jsx:44
+#: static/sentry/app/components/sidebar/userNav.jsx:37
+#: static/sentry/app/views/accountLayout.jsx:13
#: templates/sentry/bases/account.html:16 templates/sentry/bases/modal.html:18
msgid "Sign out"
msgstr ""
-#: static/sentry/app/components/sidebar/userNav.jsx:39
-#: templates/sentry/bases/account.html:35 web/forms/accounts.py:48
+#: static/sentry/app/views/accountLayout.jsx:27
+#: static/sentry/app/views/settings/account/navigationConfiguration.jsx:31
+#: templates/sentry/bases/account.html:31
+msgid "Authorized Applications"
+msgstr ""
+
+#: static/sentry/app/components/sidebar/userNav.jsx:34
+#: static/sentry/app/views/accountLayout.jsx:34
+#: static/sentry/app/views/settings/account/navigationConfiguration.jsx:7
+#: templates/sentry/bases/account.html:37 web/forms/accounts.py:49
+#: web/forms/accounts.py:237
msgid "Account"
msgstr "Rekening"
-#: templates/sentry/bases/account.html:36
+#: static/sentry/app/views/accountLayout.jsx:37
+#: static/sentry/app/views/settings/account/navigationConfiguration.jsx:11
+#: templates/sentry/bases/account.html:38
msgid "Avatar"
msgstr ""
-#: static/sentry/app/components/group/sidebar.jsx:133
-#: templates/sentry/bases/account.html:38
+#: static/sentry/app/components/group/sidebar.jsx:228
+#: static/sentry/app/views/accountLayout.jsx:43
+#: static/sentry/app/views/settings/account/navigationConfiguration.jsx:19
+#: templates/sentry/bases/account.html:40
msgid "Notifications"
msgstr ""
-#: templates/sentry/bases/account.html:39
+#: static/sentry/app/views/accountLayout.jsx:46
+#: static/sentry/app/views/settings/account/accountEmails.jsx:124
+#: static/sentry/app/views/settings/account/navigationConfiguration.jsx:23
+#: templates/sentry/bases/account.html:41
msgid "Emails"
msgstr ""
-#: templates/sentry/bases/account.html:43
+#: static/sentry/app/views/accountLayout.jsx:49
+#: templates/sentry/bases/account.html:42
msgid "Security"
msgstr ""
-#: static/sentry/app/components/sidebar/userNav.jsx:42
-#: templates/sentry/bases/admin.html:7 web/forms/__init__.py:32
+#: static/sentry/app/components/sidebar/userNav.jsx:36
+#: templates/sentry/bases/admin.html:7 web/forms/__init__.py:36
msgid "Admin"
msgstr ""
@@ -1735,7 +1767,9 @@ msgstr ""
msgid "System"
msgstr ""
-#: static/sentry/app/components/projectHeader/index.jsx:46
+#: static/sentry/app/components/projectHeader/index.jsx:67
+#: static/sentry/app/views/projectDashboard.jsx:182
+#: static/sentry/app/views/releaseDetails.jsx:174
#: templates/sentry/bases/admin.html:12
msgid "Overview"
msgstr "Oorsig"
@@ -1744,16 +1778,24 @@ msgstr "Oorsig"
msgid "Buffer"
msgstr ""
+#: templates/sentry/bases/admin.html:14
+msgid "Queue"
+msgstr "Tou"
+
#: templates/sentry/bases/admin.html:17
msgid "Mail"
msgstr "Pos"
-#: static/sentry/app/components/sidebar/organizationSelector.jsx:62
+#: static/sentry/app/components/sidebar/organizationSelector.jsx:84
+#: static/sentry/app/views/adminOrganizations.jsx:27
#: templates/sentry/bases/admin.html:24
msgid "Organizations"
msgstr ""
-#: static/sentry/app/views/stream/actions.jsx:389
+#: static/sentry/app/views/adminUsers.jsx:37
+#: static/sentry/app/views/groupDetails/header.jsx:164
+#: static/sentry/app/views/projectDashboard/eventList.jsx:101
+#: static/sentry/app/views/stream/actions.jsx:429
#: templates/sentry/bases/admin.html:26
msgid "Users"
msgstr "Gebruikers"
@@ -1762,60 +1804,92 @@ msgstr "Gebruikers"
msgid "Plugins"
msgstr ""
+#: static/sentry/app/views/settings/organization/general/organizationGeneralSettingsView.jsx:136
#: templates/sentry/bases/organization.html:8
msgid "Organization Settings"
msgstr ""
-#: static/sentry/app/components/organizations/homeSidebar.jsx:40
+#: static/sentry/app/components/organizations/homeSidebar.jsx:137
#: templates/sentry/bases/organization.html:20
msgid "Dashboard"
msgstr "Paneelbord"
-#: static/sentry/app/components/organizations/homeSidebar.jsx:44
+#: static/sentry/app/components/organizations/homeSidebar.jsx:139
+#: static/sentry/app/views/settings/organization/navigationConfiguration.jsx:16
+#: static/sentry/app/views/settings/settingsIndex.jsx:159
+#: static/sentry/app/views/settings/team/organizationTeamsView.jsx:55
#: templates/sentry/bases/organization.html:25
msgid "Projects & Teams"
msgstr ""
-#: static/sentry/app/views/organizationStats/index.jsx:243
+#: static/sentry/app/components/organizations/homeSidebar.jsx:141
+#: static/sentry/app/views/organizationStats/organizationStats.old.jsx:63
+#: static/sentry/app/views/settings/organization/navigationConfiguration.jsx:20
+#: static/sentry/app/views/settings/settingsIndex.jsx:169
#: templates/sentry/bases/organization.html:31
msgid "Stats"
msgstr "Stat."
-#: static/sentry/app/components/projectHeader/index.jsx:34
+#: static/sentry/app/components/organizations/homeSidebar.jsx:145
+#: static/sentry/app/components/projectHeader/index.jsx:58
#: templates/sentry/bases/organization.html:36
msgid "Issues"
msgstr ""
-#: static/sentry/app/components/organizations/homeSidebar.jsx:52
+#: static/sentry/app/components/organizations/homeSidebar.jsx:148
#: templates/sentry/bases/organization.html:38
msgid "Assigned to Me"
msgstr ""
-#: static/sentry/app/components/organizations/homeSidebar.jsx:53
+#: static/sentry/app/components/organizations/homeSidebar.jsx:151
+#: static/sentry/app/views/myIssues/bookmarked.jsx:12
#: templates/sentry/bases/organization.html:39
msgid "Bookmarks"
msgstr "Boekmerke"
-#: static/sentry/app/components/organizations/homeSidebar.jsx:54
+#: static/sentry/app/components/organizations/homeSidebar.jsx:154
+#: static/sentry/app/views/myIssues/viewed.jsx:12
#: templates/sentry/bases/organization.html:40
msgid "History"
msgstr ""
-#: static/sentry/app/components/organizations/homeSidebar.jsx:78
+#: static/sentry/app/components/organizations/homeSidebar.jsx:42
+#: static/sentry/app/components/sidebar/organizationSelector.jsx:109
+#: static/sentry/app/views/settings/settingsIndex.jsx:164
+#: static/sentry/app/views/teamDetails.jsx:106
+#: templates/sentry/bases/organization.html:48
+msgid "Members"
+msgstr "Lede"
+
+#: static/sentry/app/components/organizations/homeSidebar.jsx:63
+#: static/sentry/app/views/settings/organization/apiKeys/organizationApiKeysList.jsx:54
+#: static/sentry/app/views/settings/settingsIndex.jsx:254
+#: templates/sentry/bases/organization.html:69
+msgid "API Keys"
+msgstr ""
+
+#: static/sentry/app/components/organizations/homeSidebar.jsx:67
+#: static/sentry/app/views/settings/organization/auditLog/auditLogList.jsx:87
+#: static/sentry/app/views/settings/organization/auditLog/auditLogList.old.jsx:53
#: templates/sentry/bases/organization.html:75
msgid "Audit Log"
msgstr ""
-#: static/sentry/app/views/projectSettings/index.jsx:91
+#: static/sentry/app/components/organizations/homeSidebar.jsx:70
+#: static/sentry/app/views/organizationRateLimits/rateLimitView.old.jsx:144
+#: static/sentry/app/views/projectKeyDetails.jsx:344
+#: static/sentry/app/views/settings/organization/rateLimit/rateLimitView.jsx:158
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:22
#: templates/sentry/bases/organization.html:76
-#: templates/sentry/projects/manage.html:191
#: templates/sentry/projects/quotas.html:7
#: templates/sentry/projects/quotas.html:10
msgid "Rate Limits"
msgstr ""
-#: static/sentry/app/components/organizations/homeSidebar.jsx:84
-#: templates/sentry/bases/organization.html:78
+#: static/sentry/app/components/organizations/homeSidebar.jsx:77
+#: static/sentry/app/views/organizationRepositories.old.jsx:68
+#: static/sentry/app/views/settings/organization/repositories/organizationRepositories.jsx:70
+#: templates/sentry/bases/organization.html:77
msgid "Repositories"
msgstr ""
@@ -1823,42 +1897,25 @@ msgstr ""
msgid "Method:"
msgstr "Metode:"
-#: templates/sentry/groups/public_details.html:31
-msgid "Newer Event"
-msgstr ""
-
-#: templates/sentry/groups/public_details.html:32
-msgid "Older Event"
-msgstr ""
-
-#: templates/sentry/groups/public_details.html:37
-msgid ""
-"You are viewing a publicly available version of this event's data. Some "
-"information may not be available."
-msgstr ""
-
-#: templates/sentry/groups/public_details.html:43
-msgid "Full message"
-msgstr ""
-
#: templates/sentry/partial/_form.html:14
msgid "Test Configuration"
msgstr ""
-#: static/sentry/app/components/pagination.jsx:44
+#: static/sentry/app/components/pagination.jsx:61
#: templates/sentry/partial/_pager.html:5
-#: templates/sentry/users/details.html:42
-#: templates/sentry/users/details.html:59
msgid "Previous"
msgstr "Vorige"
-#: static/sentry/app/components/pagination.jsx:52
+#: static/sentry/app/components/pagination.jsx:70
#: templates/sentry/partial/_pager.html:6
-#: templates/sentry/users/details.html:43
-#: templates/sentry/users/details.html:60
msgid "Next"
msgstr "Volgende"
+#: templates/sentry/partial/form_base.html:5
+msgid "Please correct the errors below."
+msgstr "Korrigeer asseblief die foute hieronder."
+
+#: static/sentry/app/views/projectPluginDetails.jsx:133
#: templates/sentry/partial/interfaces/http_email.html:8
msgid "URL"
msgstr "URL"
@@ -1867,34 +1924,46 @@ msgstr "URL"
msgid "Method"
msgstr ""
-#: static/sentry/app/views/stream/savedSearchSelector.jsx:128
+#: static/sentry/app/views/stream/savedSearchSelector.jsx:151
#: templates/sentry/partial/interfaces/http_email.html:19
msgid "Query"
msgstr ""
-#: static/sentry/app/components/events/interfaces/richHttpContent.jsx:77
+#: static/sentry/app/components/events/interfaces/richHttpContent.jsx:57
#: templates/sentry/partial/interfaces/http_email.html:27
msgid "Fragment"
msgstr ""
-#: static/sentry/app/components/events/contextSummary.jsx:82
-#: templates/sentry/partial/interfaces/user_email.html:13
+#: static/sentry/app/components/events/contextSummary.jsx:87
+#: templates/sentry/partial/interfaces/user_email.html:14
msgid "ID:"
msgstr ""
-#: templates/sentry/partial/interfaces/user_email.html:19
+#: templates/sentry/partial/interfaces/user_email.html:20
msgid "IP Address:"
msgstr ""
-#: static/sentry/app/components/events/contextSummary.jsx:84
-#: templates/sentry/partial/interfaces/user_email.html:25
+#: static/sentry/app/components/events/contextSummary.jsx:93
+#: templates/sentry/partial/interfaces/user_email.html:26
msgid "Username:"
msgstr ""
-#: templates/sentry/partial/interfaces/user_email.html:31
+#: templates/sentry/partial/interfaces/user_email.html:32
msgid "Email:"
msgstr "E-pos:"
+#: static/sentry/app/views/inviteMember/roleSelect.jsx:26
+#: static/sentry/app/views/settings/organization/members/organizationMembersView.jsx:261
+#: static/sentry/app/views/settings/team/teamMembers.jsx:103
+#: static/sentry/app/views/settings/team/teamMembers.old.jsx:101
+#: templates/sentry/partial/members/_roles.html:5
+msgid "Role"
+msgstr ""
+
+#: templates/sentry/partial/members/_teams.html:6
+msgid "Teams"
+msgstr ""
+
#: templates/sentry/plugins/site_configuration.html:5
msgid "Changes to your configuration were saved successfully."
msgstr ""
@@ -1907,12 +1976,12 @@ msgstr ""
msgid "Link Existing"
msgstr ""
-#: static/sentry/app/plugins/components/issueActions.jsx:179
+#: static/sentry/app/plugins/components/issueActions.jsx:189
#: templates/sentry/plugins/bases/issue/create_issue.html:46
msgid "Create Issue"
msgstr ""
-#: static/sentry/app/plugins/components/issueActions.jsx:205
+#: static/sentry/app/plugins/components/issueActions.jsx:223
#: templates/sentry/plugins/bases/issue/create_issue.html:64
msgid "Link Issue"
msgstr ""
@@ -1938,216 +2007,166 @@ msgid ""
"can use it."
msgstr ""
-#: templates/sentry/projects/cannot_create_teams.html:9
-msgid ""
-"You are not able to create a new project because you are not a member of any"
-" teams. Ask an administrator to add you to a team."
+#: templates/sentry/projects/accept_project_transfer.html:6
+msgid "Accept Project Transfer"
msgstr ""
-#: templates/sentry/projects/edit_key.html:7
-#: templates/sentry/projects/edit_key.html:11
-msgid "Edit API Key"
+#: templates/sentry/projects/accept_project_transfer.html:11
+msgid "Approve Transfer Project Request"
msgstr ""
-#: templates/sentry/projects/edit_key.html:25
-msgid "Created"
+#: templates/sentry/projects/accept_project_transfer.html:18
+msgid "Please select which"
msgstr ""
-#: templates/sentry/projects/edit_key.html:38
-msgid ""
-"Your credentials are bound to public and secret key (though both should be "
-"considered semi-secret). Different clients will require different "
-"credentials, so make sure you check the documentation before plugging things"
-" in."
+#: static/sentry/app/views/inviteMember/teamSelect.jsx:23
+#: static/sentry/app/views/onboarding/project/index.jsx:51
+#: static/sentry/app/views/projectGeneralSettings.jsx:226
+#: templates/sentry/projects/accept_project_transfer.html:18
+msgid "Team"
msgstr ""
-#: static/sentry/app/views/projectInstall/overview.jsx:66
-#: templates/sentry/projects/edit_key.html:40
-#: templates/sentry/projects/keys.html:53
-msgid "DSN"
+#: templates/sentry/projects/accept_project_transfer.html:18
+msgid "you want for the project"
msgstr ""
-#: templates/sentry/projects/edit_key.html:46
-#: templates/sentry/projects/keys.html:58
-msgid "DSN (Public)"
+#: static/sentry/app/views/projectGeneralSettings.jsx:142
+#: static/sentry/app/views/projectGeneralSettings.jsx:411
+#: templates/sentry/projects/accept_project_transfer.html:26
+#: templates/sentry/projects/transfer.html:11
+msgid "Transfer Project"
msgstr ""
-#: templates/sentry/projects/edit_key.html:50
-#: templates/sentry/projects/keys.html:60
-#, python-format
+#: static/sentry/app/components/organizations/homeContainer.jsx:40
+#: static/sentry/app/components/organizations/homeContainer.jsx:51
+#: templates/sentry/projects/cannot_create_teams.html:5
+msgid "New Project"
+msgstr "Nuwe Projek"
+
+#: templates/sentry/projects/cannot_create_teams.html:9
msgid ""
-"Use your public DSN with browser-based clients such as raven-js."
+"You are not able to create a new project because you are not a member of any"
+" teams. Ask an administrator to add you to a team."
msgstr ""
-#: templates/sentry/projects/edit_key.html:53
-msgid "Public Key"
-msgstr "Publieke Sleutel"
-
-#: templates/sentry/projects/edit_key.html:59
-msgid "Secret Key"
-msgstr "Geheime Sleutel"
+#: static/sentry/app/views/projectSettings/index.jsx:98
+#: static/sentry/app/views/settings/organization/general/organizationSettingsForm.old.jsx:163
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:14
+#: templates/sentry/projects/manage.html:35
+msgid "General"
+msgstr ""
-#: templates/sentry/projects/edit_key.html:65
-msgid "Project ID"
+#: static/sentry/app/components/events/eventTags.jsx:31
+#: static/sentry/app/components/group/sidebar.jsx:210
+#: static/sentry/app/views/groupDetails/header.jsx:199
+#: static/sentry/app/views/projectSettings/index.jsx:106
+#: static/sentry/app/views/projectTags.jsx:53
+#: static/sentry/app/views/projectTags.jsx:67
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:27
+#: templates/sentry/projects/manage.html:41
+msgid "Tags"
msgstr ""
-#: static/sentry/app/views/projectSettings/index.jsx:110
-#: templates/sentry/projects/keys.html:6
-#: templates/sentry/projects/keys.html:15
-msgid "Client Keys"
+#: static/sentry/app/views/projectIssueTracking.jsx:42
+#: static/sentry/app/views/projectSettings/index.jsx:108
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:31
+#: templates/sentry/projects/manage.html:44
+msgid "Issue Tracking"
msgstr ""
-#: templates/sentry/projects/keys.html:12
-msgid "Generate New Key"
+#: static/sentry/app/views/projectReleaseTracking.jsx:142
+#: static/sentry/app/views/projectSettings/index.jsx:115
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:35
+#: templates/sentry/projects/manage.html:47
+msgid "Release Tracking"
msgstr ""
-#: static/sentry/app/components/pluginConfig.jsx:94
-#: templates/sentry/projects/keys.html:30
-msgid "Disable"
+#: static/sentry/app/views/projectDataForwarding.jsx:220
+#: static/sentry/app/views/projectSettings/index.jsx:119
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:40
+#: templates/sentry/projects/manage.html:50
+msgid "Data Forwarding"
msgstr ""
-#: templates/sentry/projects/keys.html:42
-msgid "Revoke"
+#: static/sentry/app/views/projectSavedSearches.jsx:273
+#: static/sentry/app/views/projectSettings/index.jsx:122
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:44
+#: templates/sentry/projects/manage.html:53
+msgid "Saved Searches"
msgstr ""
-#: templates/sentry/projects/keys.html:65
-msgid "CSP Endpoint"
+#: static/sentry/app/views/projectDebugSymbols.jsx:202
+#: static/sentry/app/views/projectDebugSymbols.jsx:270
+#: static/sentry/app/views/projectDebugSymbols.jsx:379
+#: static/sentry/app/views/projectSettings/index.jsx:125
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:48
+#: templates/sentry/projects/manage.html:56
+msgid "Debug Information Files"
msgstr ""
-#: templates/sentry/projects/keys.html:67
-msgid ""
-"Use your CSP endpoint in the report-uri directive in your Content-Security-Policy header."
+#: static/sentry/app/views/projectProcessingIssues.jsx:461
+#: static/sentry/app/views/projectSettings/index.jsx:131
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:52
+#: templates/sentry/projects/manage.html:59
+msgid "Processing Issues"
msgstr ""
-#: static/sentry/app/components/projectHeader/index.jsx:75
-#: templates/sentry/projects/manage.html:32
-msgid "Project Settings"
+#: static/sentry/app/views/projectSettings/index.jsx:140
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:62
+#: templates/sentry/projects/manage.html:75
+msgid "Data"
msgstr ""
-#: templates/sentry/projects/manage.html:40
-msgid "Project Details"
-msgstr "Projek Besonderhede"
+#: static/sentry/app/views/projectSettings/index.jsx:149
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:66
+#: templates/sentry/projects/manage.html:78
+msgid "Error Tracking"
+msgstr ""
-#: templates/sentry/projects/manage.html:60
-msgid "Event Settings"
+#: static/sentry/app/views/projectCspSettings.jsx:263
+#: static/sentry/app/views/projectSettings/index.jsx:152
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:70
+#: templates/sentry/projects/manage.html:81
+msgid "CSP Reports"
msgstr ""
-#: templates/sentry/projects/manage.html:76
-msgid "Client Security"
+#: static/sentry/app/components/projectHeader/index.jsx:72
+#: static/sentry/app/views/groupDetails/header.jsx:195
+#: static/sentry/app/views/projectSettings/index.jsx:155
+#: static/sentry/app/views/projectUserReportSettings.jsx:296
+#: static/sentry/app/views/projectUserReports.jsx:204
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:74
+#: templates/sentry/projects/manage.html:84
+msgid "User Feedback"
msgstr ""
-#: templates/sentry/projects/manage.html:80
-#, python-format
-msgid ""
-"Configure origin URLs which Sentry should accept events from. This is used "
-"for communication with clients like raven-js."
+#: static/sentry/app/views/projectSettings/index.jsx:158
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:78
+#: templates/sentry/projects/manage.html:87
+msgid "Inbound Filters"
msgstr ""
-#: templates/sentry/projects/manage.html:82
-msgid ""
-"This will restrict requests based on the Origin and "
-"Referer headers."
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:82
+#: templates/sentry/projects/manage.html:90
+msgid "Client Keys (DSN)"
msgstr ""
-#: templates/sentry/projects/manage.html:93
-#: templates/sentry/projects/manage.html:102
-#: templates/sentry/projects/remove.html:6
-#: templates/sentry/projects/remove.html:11
-#: templates/sentry/projects/remove.html:29
-msgid "Remove Project"
-msgstr "Verwyder Projek"
+#: static/sentry/app/components/organizations/homeSidebar.jsx:74
+#: static/sentry/app/views/organizationIntegrations.jsx:149
+#: static/sentry/app/views/projectPlugins/projectPlugins.jsx:28
+#: static/sentry/app/views/projectSettings/index.jsx:164
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:87
+#: templates/sentry/projects/manage.html:94
+msgid "Integrations"
+msgstr ""
+#: static/sentry/app/views/projectSettings/index.jsx:167
+#: static/sentry/app/views/settings/project/navigationConfiguration.jsx:91
#: templates/sentry/projects/manage.html:97
-msgid "You do not have the required permission to remove this project."
+msgid "All Integrations"
msgstr ""
-#: templates/sentry/projects/manage.html:99
-msgid ""
-"This project cannot be removed. It is used internally by the Sentry server."
-msgstr ""
-
-#: templates/sentry/projects/manage.html:168
-msgid ""
-"This option is enforced by your organization's settings and cannot be "
-"customized per-project."
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:87
-#: templates/sentry/projects/manage.html:184
-msgid "General"
-msgstr ""
-
-#: static/sentry/app/components/events/eventTags.jsx:31
-#: templates/sentry/projects/manage.html:195
-#: templates/sentry/projects/manage_tags.html:7
-#: templates/sentry/projects/manage_tags.html:10
-msgid "Tags"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:96
-#: templates/sentry/projects/manage.html:204
-msgid "Saved Searches"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:97
-#: templates/sentry/projects/manage.html:207
-msgid "Debug Symbols"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:99
-#: templates/sentry/projects/manage.html:210
-msgid "Data"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:104
-#: templates/sentry/projects/manage.html:213
-msgid "Error Tracking"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:106
-#: templates/sentry/projects/manage.html:217
-msgid "CSP Reports"
-msgstr ""
-
-#: static/sentry/app/components/projectHeader/index.jsx:51
-#: templates/sentry/projects/manage.html:221
-msgid "User Feedback"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:109
-#: templates/sentry/projects/manage.html:224
-msgid "Inbound Filters"
-msgstr ""
-
-#: templates/sentry/projects/manage.html:227
-msgid "Client Keys (DSN)"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:112
-#: templates/sentry/projects/manage.html:231
-#: templates/sentry/projects/plugins/list.html:10
-msgid "Integrations"
-msgstr ""
-
-#: static/sentry/app/views/projectSettings/index.jsx:114
-#: templates/sentry/projects/manage.html:234
-msgid "All Integrations"
-msgstr ""
-
-#: templates/sentry/projects/manage_tags.html:13
-#, python-format
-msgid ""
-"Each event in Sentry may be annotated with various tags (key and value "
-"pairs). Learn how to add custom tags."
-msgstr ""
-
-#: templates/sentry/projects/manage_tags.html:53
-msgid "We have not yet recorded any tags for this project."
-msgstr ""
-
-#: templates/sentry/projects/quotas.html:13
+#: templates/sentry/projects/quotas.html:13
msgid ""
"With the nature of Sentry, sometimes the amount of data collected can be "
"overwhelming. You can set rate limits per-project to ensure that a single "
@@ -2169,17 +2188,19 @@ msgstr ""
#: templates/sentry/projects/quotas.html:20
#, python-format
msgid ""
-"Your team has %(team_quota)s events per minute allocated "
-"collectively among projects."
-msgstr ""
-
-#: templates/sentry/projects/quotas.html:22
-#, python-format
-msgid ""
"The Sentry system has %(system_quota)s events per minute "
"allocated collectively among projects."
msgstr ""
+#: static/sentry/app/views/projectGeneralSettings.jsx:105
+#: static/sentry/app/views/projectGeneralSettings.jsx:405
+#: templates/sentry/projects/remove.html:6
+#: templates/sentry/projects/remove.html:11
+#: templates/sentry/projects/remove.html:29
+#: templates/sentry/projects/transfer.html:6
+msgid "Remove Project"
+msgstr "Verwyder Projek"
+
#: templates/sentry/projects/remove.html:18
msgid "Removing this project is permanent and cannot be undone!"
msgstr ""
@@ -2188,39 +2209,33 @@ msgstr ""
msgid "This will also remove the all associated event data."
msgstr ""
-#: templates/sentry/projects/plugins/configure.html:15
-msgid "Enable Plugin"
-msgstr ""
-
-#: templates/sentry/projects/plugins/configure.html:21
-msgid "Disable Plugin"
+#: templates/sentry/projects/transfer.html:18
+msgid "Transferring this project is permanent and cannot be undone!"
msgstr ""
-#: templates/sentry/projects/plugins/configure.html:27
-msgid "Are you sure you wish to reset all configuration for this plugin?"
+#: templates/sentry/projects/transfer.html:20
+msgid ""
+"Please enter the Owner of the organization you would like to transfer this "
+"project to."
msgstr ""
-#: templates/sentry/projects/plugins/configure.html:29
-msgid "Reset Configuration"
+#: templates/sentry/projects/transfer.html:28
+msgid "A request will be emailed to the Owner in order to transfer"
msgstr ""
-#: templates/sentry/projects/plugins/configure.html:38
-msgid "Changes to your project were saved successfully."
-msgstr "Veranderinge tot jou projek was suksesvol gestoor."
-
-#: templates/sentry/projects/plugins/list.html:7
-msgid "Manage Integrations"
+#: templates/sentry/projects/transfer.html:28
+msgid "to a new organization."
msgstr ""
-#: templates/sentry/projects/plugins/list.html:25
-msgid "n/a"
+#: templates/sentry/projects/transfer.html:31
+msgid "Send Transfer Project Request"
msgstr ""
#: templates/sentry/teams/base.html:5
msgid "Team List"
msgstr ""
-#: static/sentry/app/views/teamDetails.jsx:91
+#: static/sentry/app/views/teamDetails.jsx:100
#: templates/sentry/teams/remove.html:7 templates/sentry/teams/remove.html:12
#: templates/sentry/teams/remove.html:39
msgid "Remove Team"
@@ -2234,404 +2249,384 @@ msgstr ""
msgid "This will also remove all associated projects and events:"
msgstr ""
-#: templatetags/sentry_helpers.py:134
+#: templatetags/sentry_helpers.py:141
msgid "b"
msgstr ""
-#: templatetags/sentry_helpers.py:135
+#: templatetags/sentry_helpers.py:142
msgid "m"
msgstr ""
-#: templatetags/sentry_helpers.py:136
+#: templatetags/sentry_helpers.py:143
msgid "k"
msgstr ""
-#: templatetags/sentry_helpers.py:194
+#: templatetags/sentry_helpers.py:199
msgid "0 minutes"
msgstr "0 minute"
-#: templatetags/sentry_helpers.py:195
+#: templatetags/sentry_helpers.py:200
msgid "just now"
msgstr "nou net"
-#: static/sentry/app/views/projectDashboard.jsx:139
-#: templatetags/sentry_helpers.py:196
+#: static/sentry/app/views/projectDashboard.jsx:166
+#: templatetags/sentry_helpers.py:201
msgid "1 day"
msgstr "1 dag"
-#: templatetags/sentry_helpers.py:197
+#: templatetags/sentry_helpers.py:202
msgid "yesterday"
msgstr "gister"
-#: templatetags/sentry_helpers.py:198
-msgid " ago"
-msgstr " gelede"
+#: templatetags/sentry_helpers.py:203
+#, python-format
+msgid "%s ago"
+msgstr ""
-#: web/decorators.py:12
+#: web/decorators.py:13
msgid "The link you followed is invalid or expired."
msgstr ""
-#: web/forms/__init__.py:24
+#: web/forms/__init__.py:25
msgid ""
"Send this user a welcome email which will contain their generated password."
msgstr ""
-#: web/forms/__init__.py:33
+#: web/forms/__init__.py:37
msgid "Designates whether this user can perform administrative functions."
msgstr ""
-#: web/forms/__init__.py:34
+#: web/forms/__init__.py:41
msgid "Superuser"
msgstr ""
-#: web/forms/__init__.py:35
+#: web/forms/__init__.py:43
msgid ""
"Designates whether this user has all permissions without explicitly "
"assigning them."
msgstr ""
-#: web/forms/__init__.py:57
+#: web/forms/__init__.py:67
msgid "Disable the account."
msgstr ""
-#: web/forms/__init__.py:58
+#: web/forms/__init__.py:68
msgid "Permanently remove the user and their data."
msgstr ""
-#: web/forms/accounts.py:49
+#: web/forms/accounts.py:52 web/forms/accounts.py:239
msgid "username or email"
msgstr ""
-#: web/forms/accounts.py:54
+#: web/forms/accounts.py:58
msgid "password"
msgstr ""
-#: web/forms/accounts.py:59
+#: web/forms/accounts.py:65
#, python-format
msgid ""
"Please enter a correct %(username)s and password. Note that both fields may "
"be case-sensitive."
msgstr ""
-#: web/forms/accounts.py:61
+#: web/forms/accounts.py:69
msgid ""
"You have made too many failed authentication attempts. Please try again "
"later."
msgstr ""
-#: web/forms/accounts.py:63
+#: web/forms/accounts.py:73
msgid ""
"Your Web browser doesn't appear to have cookies enabled. Cookies are "
"required for logging in."
msgstr ""
-#: web/forms/accounts.py:65
+#: web/forms/accounts.py:77
msgid "This account is inactive."
msgstr ""
-#: web/forms/accounts.py:175
-msgid "An account is already registered with that email address."
+#: web/forms/accounts.py:195
+msgid "Subscribe to product updates newsletter"
msgstr ""
-#: web/forms/accounts.py:193
-msgid "Username or email"
+#: web/forms/accounts.py:215
+msgid "An account is already registered with that email address."
msgstr ""
-#: web/forms/accounts.py:201
+#: web/forms/accounts.py:248
msgid "We were unable to find a matching user."
msgstr ""
-#: web/forms/accounts.py:205
+#: web/forms/accounts.py:254
msgid ""
"The account you are trying to recover is managed and does not support "
"password recovery."
msgstr ""
-#: web/forms/accounts.py:208
+#: web/forms/accounts.py:260
msgid "Multiple accounts were found matching this email address."
msgstr ""
-#: web/forms/accounts.py:222
-msgid "Primary Email"
-msgstr ""
-
-#: web/forms/accounts.py:225
+#: web/forms/accounts.py:277
msgid "New Email"
msgstr ""
-#: web/forms/accounts.py:231 web/forms/accounts.py:283
+#: web/forms/accounts.py:283 web/forms/accounts.py:325
msgid "Current password"
msgstr ""
-#: web/forms/accounts.py:233
+#: web/forms/accounts.py:285
msgid "You will need to enter your current account password to make changes."
msgstr ""
-#: web/forms/accounts.py:266
+#: web/forms/accounts.py:301 web/forms/accounts.py:819
msgid "The password you entered is not correct."
msgstr ""
-#: web/forms/accounts.py:268
+#: web/forms/accounts.py:304 web/forms/accounts.py:822
msgid "You must confirm your current password to make changes."
msgstr ""
-#: web/forms/accounts.py:277
+#: web/forms/accounts.py:314
msgid "New password"
msgstr "Nuwe wagwoord"
-#: web/forms/accounts.py:334
+#: web/forms/accounts.py:320
+msgid "Verify new password"
+msgstr ""
+
+#: web/forms/accounts.py:377
+#, python-format
+msgid "There was an error adding %s: that email is already in use"
+msgstr ""
+
+#: web/forms/accounts.py:388
msgid "That username is already in use."
msgstr ""
-#: web/forms/accounts.py:381
+#: web/forms/accounts.py:456
msgid "Language"
msgstr ""
-#: web/forms/accounts.py:384
+#: web/forms/accounts.py:462
msgid "Stacktrace order"
msgstr ""
-#: web/forms/accounts.py:385
-msgid "Default (let Sentry decide)"
+#: web/forms/accounts.py:467
+msgid "Choose the default ordering of frames in stacktraces."
msgstr ""
-#: web/forms/accounts.py:386
-msgid "Most recent call last"
+#: web/forms/accounts.py:472
+msgid "Time zone"
msgstr ""
-#: web/forms/accounts.py:387
-msgid "Most recent call first"
+#: web/forms/accounts.py:478
+msgid "Use a 24-hour clock"
msgstr ""
-#: web/forms/accounts.py:388
-msgid "Choose the default ordering of frames in stacktraces."
+#: web/forms/accounts.py:559
+msgid "All deploys"
msgstr ""
-#: web/forms/accounts.py:392
-msgid "Time zone"
+#: web/forms/accounts.py:561
+msgid "Deploys with your commits"
msgstr ""
-#: web/forms/accounts.py:395
-msgid "Use a 24-hour clock"
+#: web/forms/accounts.py:561
+msgid "Never"
msgstr ""
-#: web/forms/accounts.py:484
+#: web/forms/accounts.py:599
msgid "Designate an alternative email address to send email notifications to."
msgstr ""
-#: web/forms/accounts.py:489
+#: web/forms/accounts.py:604
msgid "Automatically subscribe to alerts for new projects"
msgstr ""
-#: web/forms/accounts.py:490
+#: web/forms/accounts.py:606
msgid ""
"When enabled, you'll automatically subscribe to alerts when you create or "
"join a project."
msgstr ""
-#: web/forms/accounts.py:495
-msgid "Automatically subscribe to workflow notifications for new projects"
+#: web/forms/accounts.py:612
+msgid "Preferred workflow subscription level for new projects"
msgstr ""
-#: web/forms/accounts.py:496
+#: web/forms/accounts.py:619
msgid ""
-"When enabled, you'll automatically subscribe to workflow notifications when "
-"you create or join a project."
+"This will be automatically set as your subscription preference when you "
+"create or join a project. It has no effect on existing projects."
msgstr ""
-#: web/forms/accounts.py:500
+#: web/forms/accounts.py:624
msgid "Receive notifications about my own activity"
msgstr ""
-#: web/forms/accounts.py:501
+#: web/forms/accounts.py:626
msgid ""
"Enable this if you wish to receive emails for your own actions, as well as "
"others."
msgstr ""
-#: web/forms/accounts.py:633
-msgid "One-time password"
+#: web/forms/accounts.py:632
+msgid "Claim unassigned issues when resolving them"
msgstr ""
#: web/forms/accounts.py:634
-msgid "Code from authenticator"
-msgstr ""
-
-#: web/forms/accounts.py:642
-msgid "Sentry account password"
+msgid ""
+"When enabled, you'll automatically be assigned to unassigned issues when "
+"marking them as resolved."
msgstr ""
-#: web/forms/add_project.py:17
-msgid "i.e. API, Frontend, My Application Name"
+#: web/forms/accounts.py:788
+msgid "One-time password"
msgstr ""
-#: web/forms/add_project.py:19
-msgid "Using the repository name generally works well."
+#: web/forms/accounts.py:792
+msgid "Code from authenticator"
msgstr ""
-#: web/forms/add_team.py:15
-msgid "E.g. Operations, Web, Desktop, ..."
+#: web/forms/accounts.py:801
+msgid "Sentry account password"
msgstr ""
-#: web/forms/fields.py:83
+#: web/forms/fields.py:85
msgid "Invalid username"
msgstr ""
-#: web/forms/fields.py:105
+#: web/forms/fields.py:107
msgid "Not set"
msgstr ""
-#: web/forms/fields.py:129
+#: web/forms/fields.py:131
msgid "e.g. example.com or https://example.com"
msgstr ""
-#: web/forms/fields.py:158
-msgid "e.g. 127.0.0.1 or 10.0.0.0/8"
-msgstr ""
-
-#: web/forms/projects.py:20
+#: web/forms/projects.py:18
msgid "Minimum delivery frequency"
msgstr ""
-#: static/sentry/app/views/projectAlertSettings.jsx:105
-#: web/forms/projects.py:21
+#: static/sentry/app/views/projectAlertSettings.jsx:53
+#: web/forms/projects.py:19
msgid "Notifications will be delivered at most this often."
msgstr ""
-#: web/forms/projects.py:26
+#: web/forms/projects.py:25
msgid "Maximum delivery frequency"
msgstr ""
-#: static/sentry/app/views/projectAlertSettings.jsx:119
-#: web/forms/projects.py:27
+#: static/sentry/app/views/projectAlertSettings.jsx:65
+#: web/forms/projects.py:26
msgid "Notifications will be delivered at least this often."
msgstr ""
-#: web/forms/projects.py:35
+#: web/forms/projects.py:37
msgid ""
"Maximum delivery frequency must be equal to or greater than the minimum "
"delivery frequency."
msgstr ""
-#: web/forms/projects.py:41
-msgid "Maximum events per minute"
-msgstr ""
-
-#: web/forms/projects.py:43
-msgid ""
-"This cannot be higher than the team (or system) allotted maximum. The value "
-"can be either a fixed number, or a percentage that is relative to the team's"
-" overall quota."
-msgstr ""
-
-#: web/forms/projects.py:80
+#: static/sentry/app/views/settings/organization/apiKeys/organizationApiKeyDetailsView.jsx:86
+#: web/forms/projects.py:45
msgid "Label"
msgstr ""
-#: web/frontend/accept_organization_invite.py:16
+#: web/frontend/accept_organization_invite.py:14
msgid "The invite link you followed is not valid."
msgstr ""
-#: web/frontend/accept_organization_invite.py:107
+#: web/frontend/accept_organization_invite.py:118
#, python-format
msgid "You are already a member of the %r organization."
msgstr ""
-#: web/frontend/accept_organization_invite.py:129
+#: web/frontend/accept_organization_invite.py:139
#, python-format
msgid "You have been added to the %r organization."
msgstr ""
-#: web/frontend/accounts.py:145
+#: web/frontend/accept_project_transfer.py:52
+msgid "Could not approve transfer, please make sure link is valid."
+msgstr ""
+
+#: web/frontend/accept_project_transfer.py:60
+msgid "Project transfer link has expired!"
+msgstr ""
+
+#: web/frontend/accept_project_transfer.py:73
+msgid "Invalid permissions!"
+msgstr ""
+
+#: web/frontend/accept_project_transfer.py:94
+msgid "Project no longer exists"
+msgstr ""
+
+#: web/frontend/accounts.py:188
msgid "There was an error confirming your email."
msgstr ""
-#: web/frontend/accounts.py:149 web/frontend/accounts.py:156
+#: web/frontend/accounts.py:192 web/frontend/accounts.py:199
#, python-format
msgid "A verification email has been sent to %s."
msgstr ""
-#: web/frontend/accounts.py:158
+#: web/frontend/accounts.py:210
#, python-format
msgid "Your email (%s) has already been verified."
msgstr ""
-#: web/frontend/accounts.py:164
+#: web/frontend/accounts.py:216
msgid "Thanks for confirming your email"
msgstr ""
-#: web/frontend/accounts.py:172
+#: web/frontend/accounts.py:225
msgid ""
"There was an error confirming your email. Please try again or visit your "
"Account Settings to resend the verification email."
msgstr ""
-#: web/frontend/accounts.py:222 web/frontend/accounts.py:435
-#: web/frontend/accounts.py:457
+#: web/frontend/accounts.py:296 web/frontend/accounts.py:585
#, python-format
msgid "A confirmation email has been sent to %s."
msgstr ""
-#: web/frontend/accounts.py:229 web/frontend/accounts.py:464
+#: web/frontend/accounts.py:301 web/frontend/accounts.py:590
msgid "Your settings were saved."
msgstr ""
-#: web/frontend/accounts_twofactor.py:26
-msgid "Phone number"
-msgstr ""
-
-#: web/frontend/accounts_twofactor.py:32
-msgid "Device name"
-msgstr ""
-
-#: web/frontend/auth_login.py:20
-msgid ""
-"The organization does not exist or does not have Single Sign-On enabled."
-msgstr ""
-
-#: web/frontend/create_organization.py:14
-msgid "Organization Name"
-msgstr ""
-
-#: web/frontend/create_organization.py:15
-msgid "My Company"
+#: web/frontend/accounts.py:523
+msgid "That email is already in use for another user"
msgstr ""
-#: web/frontend/create_organization_member.py:62
-#, python-format
-msgid "The organization member %s was added."
-msgstr ""
-
-#: web/frontend/create_organization_member.py:68
-#, python-format
-msgid "The organization member %s already exists."
+#: web/frontend/accounts.py:544
+msgid "Your settings were saved"
msgstr ""
-#: web/frontend/disable_project_key.py:32
-#, python-format
-msgid "The API key (%s) was disabled."
+#: web/frontend/accounts_twofactor.py:25
+msgid "Phone number"
msgstr ""
-#: web/frontend/edit_project_key.py:38
-#, python-format
-msgid "Changes to the API key (%s) were saved."
+#: web/frontend/accounts_twofactor.py:30
+msgid "Device name"
msgstr ""
-#: web/frontend/enable_project_key.py:32
-#, python-format
-msgid "The API key (%s) was enabled."
+#: web/frontend/auth_login.py:21
+msgid ""
+"The organization does not exist or does not have Single Sign-On enabled."
msgstr ""
-#: web/frontend/error_page_embed.py:23
+#: web/frontend/error_page_embed.py:22
msgid ""
"An unknown error occurred while submitting your report. Please try again."
msgstr ""
-#: web/frontend/error_page_embed.py:24
+#: web/frontend/error_page_embed.py:23
msgid "Some fields were invalid. Please correct the errors and try again."
msgstr ""
-#: web/frontend/error_page_embed.py:25
+#: web/frontend/error_page_embed.py:24
msgid "Your feedback has been sent. Thank you!"
msgstr ""
@@ -2639,2472 +2634,4584 @@ msgstr ""
msgid "Jane Doe"
msgstr ""
-#: web/frontend/error_page_embed.py:33
+#: web/frontend/error_page_embed.py:36
msgid "jane@example.com"
msgstr ""
-#: web/frontend/error_page_embed.py:37
+#: web/frontend/error_page_embed.py:43
msgid "I clicked on 'X' and then hit 'Confirm'"
msgstr ""
-#: web/frontend/organization_api_key_settings.py:15
-#: web/frontend/project_settings.py:30
-msgid "Allowed Domains"
-msgstr ""
-
-#: static/sentry/app/views/projectCspSettings.jsx:97
-#: web/frontend/organization_api_key_settings.py:16
-#: web/frontend/project_settings.py:31 web/frontend/project_settings.py:81
-msgid "Separate multiple entries with a newline."
-msgstr ""
-
-#: web/frontend/organization_auth_settings.py:21
-msgid "The SSO feature is not enabled for this organization."
-msgstr ""
-
-#: web/frontend/organization_auth_settings.py:23
+#: web/frontend/organization_auth_settings.py:24
msgid "SSO authentication has been disabled."
msgstr ""
-#: web/frontend/organization_auth_settings.py:25
+#: web/frontend/organization_auth_settings.py:27
msgid ""
"A reminder email has been sent to members who have not yet linked their "
"accounts."
msgstr ""
-#: web/frontend/organization_auth_settings.py:30
+#: web/frontend/organization_auth_settings.py:33
msgid "Require SSO"
msgstr ""
-#: web/frontend/organization_auth_settings.py:31
+#: web/frontend/organization_auth_settings.py:34
msgid ""
"Require members use a valid linked SSO account to access this organization"
msgstr ""
-#: web/frontend/organization_auth_settings.py:35
-#: web/frontend/organization_settings.py:27
+#: static/sentry/app/views/settings/organization/general/organizationSettingsForm.old.jsx:202
+#: web/frontend/organization_auth_settings.py:38
msgid "Default Role"
msgstr ""
-#: web/frontend/organization_auth_settings.py:37
+#: web/frontend/organization_auth_settings.py:41
msgid ""
"The default role new members will receive when logging in for the first "
"time."
msgstr ""
-#: web/frontend/organization_member_settings.py:36
-#, python-format
-msgid "A new invitation has been generated and sent to %(email)s"
+#: web/frontend/remove_organization.py:15
+msgid "You cannot remove the default organization."
msgstr ""
-#: web/frontend/organization_member_settings.py:41
+#: web/frontend/remove_organization.py:17
#, python-format
-msgid "An invitation to join %(organization)s has been sent to %(email)s"
+msgid "The %s organization has been scheduled for removal."
msgstr ""
-#: web/frontend/organization_member_settings.py:93
-msgid "Your changes were saved."
+#: web/frontend/remove_project.py:38
+#, python-format
+msgid "The project %r was scheduled for deletion."
msgstr ""
-#: web/frontend/organization_settings.py:16
-msgid "The name of your organization. i.e. My Company"
+#: web/frontend/remove_team.py:36
+#, python-format
+msgid "The team %r was scheduled for deletion."
msgstr ""
-#: static/sentry/app/views/teamSettings.jsx:88
-#: web/frontend/organization_settings.py:18
-#: web/frontend/project_settings.py:26
-msgid "Short name"
+#: web/frontend/restore_organization.py:16
+msgid "Deletion already canceled."
msgstr ""
-#: web/frontend/organization_settings.py:19
-msgid "A unique ID used to identify this organization."
+#: web/frontend/restore_organization.py:17
+msgid "Deletion cannot be canceled, already in progress"
msgstr ""
-#: web/frontend/organization_settings.py:22
-msgid "Open Membership"
+#: web/frontend/restore_organization.py:20
+msgid "Organization restored successfully."
msgstr ""
-#: web/frontend/organization_settings.py:23
-msgid "Allow organization members to freely join or leave any team."
+#: web/frontend/transfer_project.py:24
+msgid "Organization Owner"
msgstr ""
-#: web/frontend/organization_settings.py:29
-msgid "The default role new members will receive."
+#: web/frontend/transfer_project.py:26
+msgid "user@company.com"
msgstr ""
-#: web/frontend/organization_settings.py:32
-msgid "Enhanced Privacy"
+#: web/frontend/transfer_project.py:53
+msgid "Could not find owner with that email"
msgstr ""
-#: web/frontend/organization_settings.py:33
-msgid ""
-"Enable enhanced privacy controls to limit personally identifiable "
-"information (PII) as well as source code in things like notifications."
+#: web/frontend/transfer_project.py:92
+#, python-format
+msgid "A request was sent to move project %r to a different organization"
msgstr ""
-#: web/frontend/organization_settings.py:37
-msgid "Allow Shared Issues"
+#: web/frontend/twofactor.py:41
+msgid "Invalid confirmation code. Try again."
msgstr ""
-#: web/frontend/organization_settings.py:38
-msgid "Enable sharing of limited details on issues to anonymous users."
+#: static/sentry/app/actionCreators/plugins.jsx:100
+msgid "Unable to disable plugin"
msgstr ""
-#: web/frontend/organization_settings.py:42
-msgid "Require Data Scrubber"
+#: static/sentry/app/actionCreators/plugins.jsx:82
+msgid "Enabling..."
msgstr ""
-#: web/frontend/organization_settings.py:43
-msgid "Require server-side data scrubbing be enabled for all projects."
+#: static/sentry/app/actionCreators/plugins.jsx:84
+msgid "Plugin was enabled"
msgstr ""
-#: web/frontend/organization_settings.py:47
-msgid "Require Using Default Scrubbers"
+#: static/sentry/app/actionCreators/plugins.jsx:85
+msgid "Unable to enable plugin"
msgstr ""
-#: web/frontend/organization_settings.py:48
-msgid ""
-"Require the default scrubbers be applied to prevent things like passwords "
-"and credit cards from being stored for all projects."
+#: static/sentry/app/actionCreators/plugins.jsx:97
+msgid "Disabling..."
msgstr ""
-#: web/frontend/organization_settings.py:52
-msgid "Global additional sensitive fields"
+#: static/sentry/app/actionCreators/plugins.jsx:99
+msgid "Plugin was disabled"
msgstr ""
-#: web/frontend/organization_settings.py:53
-msgid ""
-"Additional field names to match against when scrubbing data for all "
-"projects. Separate multiple entries with a newline. Note: These"
-" fields will be used in addition to project specific fields."
+#: static/sentry/app/components/actionOverlay.jsx:60
+msgid "Do this later …"
msgstr ""
-#: web/frontend/organization_settings.py:56
-#: web/frontend/organization_settings.py:67
-#: web/frontend/project_settings.py:51 web/frontend/project_settings.py:62
-msgid "e.g. email"
+#: static/sentry/app/components/actions/ignore.jsx:105
+msgid "Ignore this issue until it occurs again .. "
msgstr ""
-#: web/frontend/organization_settings.py:63
-msgid "Global safe fields"
+#: static/sentry/app/components/actions/ignore.jsx:106
+msgid "Number of times"
msgstr ""
-#: web/frontend/organization_settings.py:64
-msgid ""
-"Field names which data scrubbers should ignore. Separate multiple entries "
-"with a newline. Note: These fields will be used in addition to "
-"project specific fields."
+#: static/sentry/app/components/actions/ignore.jsx:115
+msgid "Ignore this issue until it affects an additional .. "
msgstr ""
-#: web/frontend/organization_settings.py:74
-msgid "Prevent Storing of IP Addresses"
+#: static/sentry/app/components/actions/ignore.jsx:116
+msgid "Numbers of users"
msgstr ""
-#: web/frontend/organization_settings.py:75
-msgid ""
-"Preventing IP addresses from being stored for new events on all projects."
+#: static/sentry/app/components/actions/ignore.jsx:128
+#: static/sentry/app/components/customIgnoreCountModal.jsx:84
+#: static/sentry/app/components/customIgnoreDurationModal.jsx:118
+msgid "Ignore"
msgstr ""
-#: web/frontend/organization_settings.py:79
-msgid "Early Adopter"
+#: static/sentry/app/components/actions/ignore.jsx:160
+#: static/sentry/app/components/actions/ignore.jsx:210
+#: static/sentry/app/components/actions/ignore.jsx:260
+msgid "Custom"
msgstr ""
-#: web/frontend/organization_settings.py:80
-msgid "Opt-in to new features before they're released to the public."
+#: static/sentry/app/components/actions/ignore.jsx:175
+#, python-format
+msgid "%s times"
msgstr ""
-#: web/frontend/organization_settings.py:163
-msgid "Changes to your organization were saved."
+#: static/sentry/app/components/actions/ignore.jsx:185
+#: static/sentry/app/components/actions/ignore.jsx:235
+msgid "from now"
msgstr ""
-#: web/frontend/project_quotas.py:12
-msgid "The quotas feature is not enabled for this project."
+#: static/sentry/app/components/actions/ignore.jsx:225
+#, python-format
+msgid "%s users"
msgstr ""
-#: web/frontend/project_release_tracking.py:21
-msgid ""
-"Your deploy token has been regenerated. You will need to update any pre-"
-"existing deploy hooks."
+#: static/sentry/app/components/actions/ignore.jsx:85
+msgid "Change status to unresolved"
msgstr ""
-#: web/frontend/project_release_tracking.py:23
-msgid "The release tracking feature is not enabled for this project."
+#: static/sentry/app/components/actions/resolve.jsx:104
+msgid "Set up release tracking in order to use this feature."
msgstr ""
-#: web/frontend/project_settings.py:23
-msgid "Project Name"
+#: static/sentry/app/components/actions/resolve.jsx:131
+msgid "Resolve"
msgstr ""
-#: web/frontend/project_settings.py:24
-msgid "Production"
+#: static/sentry/app/components/actions/resolve.jsx:142
+msgid "Resolved In"
msgstr ""
-#: web/frontend/project_settings.py:27
-msgid "A unique ID used to identify this project."
+#: static/sentry/app/components/actions/resolve.jsx:160
+msgid "The next release"
msgstr ""
-#: web/frontend/project_settings.py:32
-msgid "Security token"
+#: static/sentry/app/components/actions/resolve.jsx:179
+#, python-format
+msgid "The current release (%s)"
msgstr ""
-#: web/frontend/project_settings.py:33
-#, python-brace-format
-msgid ""
-"Outbound requests matching Allowed Domains will have the header \"X-Sentry-"
-"Token: {token}\" appended."
+#: static/sentry/app/components/actions/resolve.jsx:180
+msgid "The current release"
msgstr ""
-#: web/frontend/project_settings.py:34
-msgid "Auto resolve"
+#: static/sentry/app/components/actions/resolve.jsx:190
+msgid "Another version ..."
msgstr ""
-#: web/frontend/project_settings.py:36
+#: static/sentry/app/components/actions/resolve.jsx:59
msgid ""
-"Automatically resolve an issue if it hasn't been seen for this amount of "
-"time."
+"This event is resolved due to the Auto Resolve configuration for this "
+"project"
msgstr ""
-#: web/frontend/project_settings.py:38
-msgid "Data Scrubber"
+#: static/sentry/app/components/actions/resolve.jsx:72
+msgid "Unresolve"
msgstr ""
-#: web/frontend/project_settings.py:39
-msgid "Enable server-side data scrubbing."
+#: static/sentry/app/components/activity/feed.jsx:124
+#: static/sentry/app/components/issueList.jsx:128
+msgid "Nothing to show here, move along."
msgstr ""
-#: web/frontend/project_settings.py:43
-msgid "Use Default Scrubbers"
+#: static/sentry/app/components/activity/item.jsx:102
+#: static/sentry/app/components/activity/item.jsx:114
+msgid "[author] marked [issue] as fixed in [version]"
msgstr ""
-#: web/frontend/project_settings.py:44
-msgid ""
-"Apply default scrubbers to prevent things like passwords and credit cards "
-"from being stored."
+#: static/sentry/app/components/activity/item.jsx:126
+msgid "[author] marked [issue] as unresolved"
msgstr ""
-#: web/frontend/project_settings.py:48
-msgid "Additional sensitive fields"
+#: static/sentry/app/components/activity/item.jsx:132
+msgid "[author] ignored [issue] for [duration]"
msgstr ""
-#: web/frontend/project_settings.py:49
+#: static/sentry/app/components/activity/item.jsx:138
msgid ""
-"Additional field names to match against when scrubbing data. Separate "
-"multiple entries with a newline."
+"[author] ignored [issue] until it happens [count] time(s) in [duration]"
msgstr ""
-#: web/frontend/project_settings.py:58
-msgid "Safe fields"
+#: static/sentry/app/components/activity/item.jsx:148
+msgid "[author] ignored [issue] until it happens [count] time(s)"
msgstr ""
-#: web/frontend/project_settings.py:59
+#: static/sentry/app/components/activity/item.jsx:154
msgid ""
-"Field names which data scrubbers should ignore. Separate multiple entries "
-"with a newline."
+"[author] ignored [issue] until it affects [count] user(s) in [duration]"
msgstr ""
-#: web/frontend/project_settings.py:69
-msgid "Don't store IP Addresses"
+#: static/sentry/app/components/activity/item.jsx:164
+msgid "[author] ignored [issue] until it affects [count] user(s)"
msgstr ""
-#: web/frontend/project_settings.py:70
-msgid "Prevent IP addresses from being stored for new events."
+#: static/sentry/app/components/activity/item.jsx:170
+msgid "[author] ignored [issue]"
msgstr ""
-#: web/frontend/project_settings.py:76
-msgid "Enable JavaScript source fetching"
+#: static/sentry/app/components/activity/item.jsx:175
+msgid "[author] made [issue] public"
msgstr ""
-#: web/frontend/project_settings.py:77
-msgid ""
-"Allow Sentry to scrape missing JavaScript source context when possible."
+#: static/sentry/app/components/activity/item.jsx:180
+msgid "[author] made [issue] private"
msgstr ""
-#: web/frontend/project_settings.py:80
-msgid "Filtered IP Addresses"
+#: static/sentry/app/components/activity/item.jsx:186
+msgid "[author] marked [issue] as a regression in [version]"
msgstr ""
-#: web/frontend/project_settings.py:87
-msgid "Default Environment"
+#: static/sentry/app/components/activity/item.jsx:194
+msgid "[author] marked [issue] as a regression"
msgstr ""
-#: web/frontend/project_settings.py:88
-msgid "The default selected environment when viewing issues."
+#: static/sentry/app/components/activity/item.jsx:199
+msgid "[author] linked [issue] on [provider]"
msgstr ""
-#: web/frontend/project_settings.py:89
-msgid "e.g. production"
+#: static/sentry/app/components/activity/item.jsx:205
+msgid "%2$s migrated %1$d fingerprint from %3$s to %4$s"
+msgid_plural "%2$s migrated %1$d fingerprints from %3$s to %4$s"
+msgstr[0] ""
+msgstr[1] ""
+
+#: static/sentry/app/components/activity/item.jsx:215
+#: static/sentry/app/views/groupActivity/index.jsx:148
+#: static/sentry/app/views/groupActivity/index.jsx:162
+msgid "a group"
msgstr ""
-#: web/frontend/project_settings.py:93
-msgid "Subject Prefix"
+#: static/sentry/app/components/activity/item.jsx:220
+msgid "[author] saw [link:issue]"
msgstr ""
-#: web/frontend/project_settings.py:94
-msgid "Choose a custom prefix for emails from this project."
+#: static/sentry/app/components/activity/item.jsx:227
+msgid "[author] assigned [issue] to themselves"
msgstr ""
-#: web/frontend/project_settings.py:276
-msgid "Changes to your project were saved."
+#: static/sentry/app/components/activity/item.jsx:234
+#: static/sentry/app/components/activity/item.jsx:240
+msgid "[author] assigned [issue] to [assignee]"
msgstr ""
-#: web/frontend/remove_organization.py:15
-msgid "You cannot remove the default organization."
+#: static/sentry/app/components/activity/item.jsx:246
+msgid "[author] assigned [issue] to an [help:unknown user]"
msgstr ""
-#: web/frontend/remove_organization.py:17
-#, python-format
-msgid "The %s organization has been scheduled for removal."
+#: static/sentry/app/components/activity/item.jsx:252
+msgid "[author] unassigned [issue]"
msgstr ""
-#: web/frontend/remove_project.py:35
+#: static/sentry/app/components/activity/item.jsx:257
+msgid "[author] merged [count] [link:issues]"
+msgstr ""
+
+#: static/sentry/app/components/activity/item.jsx:263
+msgid "[author] released version [version]"
+msgstr ""
+
+#: static/sentry/app/components/activity/item.jsx:270
+msgid "[author] deployed version [version] to [environment]."
+msgstr ""
+
+#: static/sentry/app/components/activity/item.jsx:67
+msgid "[author] commented on [issue]"
+msgstr ""
+
+#: static/sentry/app/components/activity/item.jsx:78
+msgid "[author] marked [issue] as resolved"
+msgstr ""
+
+#: static/sentry/app/components/activity/item.jsx:83
+msgid "[author] marked [issue] as resolved due to age"
+msgstr ""
+
+#: static/sentry/app/components/activity/item.jsx:89
+msgid "[author] marked [issue] as resolved in [version]"
+msgstr ""
+
+#: static/sentry/app/components/activity/item.jsx:97
+msgid "[author] marked [issue] as resolved in the upcoming release"
+msgstr ""
+
+#: static/sentry/app/components/activity/note.jsx:34
+#: static/sentry/app/components/activity/noteInput.jsx:258
+msgid "Edit"
+msgstr ""
+
+#: static/sentry/app/components/activity/note.jsx:38
+msgid "Are you sure you wish to delete this comment?"
+msgstr ""
+
+#: static/sentry/app/components/activity/noteInput.jsx:124
+msgid "Posting comment.."
+msgstr ""
+
+#: static/sentry/app/components/activity/noteInput.jsx:160
+msgid "Updating comment.."
+msgstr ""
+
+#: static/sentry/app/components/activity/noteInput.jsx:18
+msgid "Unknown error. Please try again."
+msgstr ""
+
+#: static/sentry/app/components/activity/noteInput.jsx:251
+msgid "Post Comment"
+msgstr ""
+
+#: static/sentry/app/components/activity/noteInput.jsx:251
+msgid "Save Comment"
+msgstr ""
+
+#: static/sentry/app/components/activity/noteInput.jsx:258
+msgid "Write"
+msgstr ""
+
+#: static/sentry/app/components/activity/noteInput.jsx:261
+msgid "Preview"
+msgstr ""
+
+#: static/sentry/app/components/activity/noteInput.jsx:265
+msgid "Markdown supported"
+msgstr ""
+
+#: static/sentry/app/components/activity/noteInput.jsx:276
+msgid "Add details or updates to this event"
+msgstr ""
+
+#: static/sentry/app/components/assigneeSelector.jsx:231
+msgid "No matching users found."
+msgstr ""
+
+#: static/sentry/app/components/assigneeSelector.jsx:263
+msgid "Filter people"
+msgstr ""
+
+#: static/sentry/app/components/assigneeSelector.jsx:278
+msgid "Clear Assignee"
+msgstr ""
+
+#: static/sentry/app/components/avatarCropper.jsx:390
+msgid "Change Photo"
+msgstr ""
+
+#: static/sentry/app/components/avatarRadio.jsx:48
+msgid "Avatar Type"
+msgstr ""
+
+#: static/sentry/app/components/avatarSettings.jsx:122
+#: static/sentry/app/views/settings/account/avatar.jsx:145
+msgid "Done"
+msgstr ""
+
+#: static/sentry/app/components/avatarSettings.jsx:62
+#: static/sentry/app/views/settings/account/avatar.jsx:67
+msgid "Successfully saved avatar preferences"
+msgstr ""
+
+#: static/sentry/app/components/avatarSettings.jsx:98
+#: static/sentry/app/views/settings/account/avatar.jsx:109
+msgid "Gravatars are managed through "
+msgstr ""
+
+#: static/sentry/app/components/bases/pluginComponentBase.jsx:105
+msgid "Success!"
+msgstr ""
+
+#: static/sentry/app/components/bases/pluginComponentBase.jsx:117
+#: static/sentry/app/views/accountAuthorizations.jsx:47
+#: static/sentry/app/views/organizationRateLimits/rateLimitView.old.jsx:118
+#: static/sentry/app/views/projectAlertRules.jsx:53
+#: static/sentry/app/views/projectFilters.jsx:605
+#: static/sentry/app/views/settings/account/accountAuthorizations.jsx:92
+#: static/sentry/app/views/settings/organization/apiKeys/organizationApiKeyDetailsView.jsx:59
+#: static/sentry/app/views/settings/organization/general/organizationSettingsForm.old.jsx:110
+#: static/sentry/app/views/settings/organization/rateLimit/rateLimitView.jsx:133
+msgid "Unable to save changes. Please try again."
+msgstr ""
+
+#: static/sentry/app/components/bases/pluginComponentBase.jsx:77
+#: static/sentry/app/views/organizationIntegrations.jsx:69
+#: static/sentry/app/views/organizationRepositoriesView.jsx:35
+#: static/sentry/app/views/organizationRepositoriesView.jsx:62
+msgid "An error occurred."
+msgstr ""
+
+#: static/sentry/app/components/bases/pluginComponentBase.jsx:92
+#: static/sentry/app/components/compactIssue.jsx:151
+#: static/sentry/app/components/forms/apiForm.jsx:41
+#: static/sentry/app/components/group/sidebar.jsx:78
+#: static/sentry/app/views/accountAuthorizations.jsx:37
+#: static/sentry/app/views/apiApplicationDetails.jsx:96
+#: static/sentry/app/views/apiApplications.jsx:39
+#: static/sentry/app/views/apiApplications.jsx:137
+#: static/sentry/app/views/apiTokens.jsx:41
+#: static/sentry/app/views/groupDetails/actions.jsx:139
+#: static/sentry/app/views/groupDetails/header.jsx:41
+#: static/sentry/app/views/organizationIntegrations.jsx:58
+#: static/sentry/app/views/organizationRepositoriesView.jsx:20
+#: static/sentry/app/views/organizationRepositoriesView.jsx:46
+#: static/sentry/app/views/projectAlertRules.jsx:39
+#: static/sentry/app/views/projectCspSettings.jsx:58
+#: static/sentry/app/views/projectFilters.jsx:298
+#: static/sentry/app/views/projectFilters.jsx:576
+#: static/sentry/app/views/projectKeyDetails.jsx:207
+#: static/sentry/app/views/projectKeyDetails.jsx:237
+#: static/sentry/app/views/projectKeys.jsx:47
+#: static/sentry/app/views/projectKeys.jsx:67
+#: static/sentry/app/views/projectKeys.jsx:290
+#: static/sentry/app/views/projectPluginDetails.jsx:43
+#: static/sentry/app/views/projectProcessingIssues.jsx:432
+#: static/sentry/app/views/projectSavedSearches.jsx:39
+#: static/sentry/app/views/projectSavedSearches.jsx:59
+#: static/sentry/app/views/projectUserReportSettings.jsx:57
+#: static/sentry/app/views/settings/account/accountAuthorizations.jsx:80
+#: static/sentry/app/views/settings/account/apiApplications.jsx:47
+#: static/sentry/app/views/settings/account/apiApplications.jsx:114
+#: static/sentry/app/views/settings/account/apiTokens.jsx:34
+#: static/sentry/app/views/settings/components/forms/apiForm.jsx:28
+#: static/sentry/app/views/settings/organization/general/organizationSettingsForm.old.jsx:83
+#: static/sentry/app/views/stream/actions.jsx:188
+#: static/sentry/app/views/stream/savedSearchSelector.jsx:83
+msgid "Saving changes.."
+msgstr ""
+
+#: static/sentry/app/components/clippedBox.jsx:20
+msgid "Show More"
+msgstr ""
+
+#: static/sentry/app/components/commitAuthorStats.jsx:84
+msgid "No authors in this release"
+msgstr ""
+
+#: static/sentry/app/components/customIgnoreCountModal.jsx:56
+msgid "e.g. 100"
+msgstr ""
+
+#: static/sentry/app/components/customIgnoreCountModal.jsx:60
+msgid "Time window"
+msgstr ""
+
+#: static/sentry/app/components/customIgnoreCountModal.jsx:66
+msgid "e.g. per hour"
+msgstr ""
+
+#: static/sentry/app/components/customIgnoreCountModal.jsx:68
+msgid "(Optional) If supplied, this rule will apply as a rate of change."
+msgstr ""
+
+#: static/sentry/app/components/customIgnoreDurationModal.jsx:106
+msgid "Please enter a valid date in the future"
+msgstr ""
+
+#: static/sentry/app/components/customIgnoreDurationModal.jsx:17
+msgid "Ignore this issue until it occurs after .."
+msgstr ""
+
+#: static/sentry/app/components/customIgnoreDurationModal.jsx:79
+msgid "Date"
+msgstr ""
+
+#: static/sentry/app/components/customIgnoreDurationModal.jsx:91
+msgid "Time (UTC)"
+msgstr ""
+
+#: static/sentry/app/components/customResolutionModal.jsx:61
+msgid "e.g. 1.0.4"
+msgstr ""
+
+#: static/sentry/app/components/errors/detailedError.jsx:46
+#: static/sentry/app/components/loadingError.jsx:30
+msgid "Retry"
+msgstr ""
+
+#: static/sentry/app/components/errors/groupEventDetailsLoadingError.jsx:10
+msgid "The events have been deleted"
+msgstr ""
+
+#: static/sentry/app/components/errors/groupEventDetailsLoadingError.jsx:11
+msgid "There is an internal systems error or active issue"
+msgstr ""
+
+#: static/sentry/app/components/errors/groupEventDetailsLoadingError.jsx:18
+msgid "Sorry, the events for this issue could not be found."
+msgstr ""
+
+#: static/sentry/app/components/errors/groupEventDetailsLoadingError.jsx:21
+msgid "This could be due to a handful of reasons:"
+msgstr ""
+
+#: static/sentry/app/components/errors/groupEventDetailsLoadingError.jsx:9
+msgid "The events are still processing and are on their way"
+msgstr ""
+
+#: static/sentry/app/components/events/contextSummary.jsx:111
+msgid "Unknown Device"
+msgstr ""
+
+#: static/sentry/app/components/events/contextSummary.jsx:144
+#: static/sentry/app/components/events/contextSummary.jsx:151
+#: static/sentry/app/components/events/contextSummary.jsx:165
+#: static/sentry/app/components/events/contextSummary.jsx:181
+msgid "Unknown OS"
+msgstr ""
+
+#: static/sentry/app/components/events/contextSummary.jsx:160
+msgid "Unknown Browser"
+msgstr ""
+
+#: static/sentry/app/components/events/contextSummary.jsx:176
+msgid "Unknown Runtime"
+msgstr ""
+
+#: static/sentry/app/components/events/contextSummary.jsx:52
+msgid "Unknown"
+msgstr ""
+
+#: static/sentry/app/components/events/contextSummary.jsx:52
+msgid "Version:"
+msgstr ""
+
+#: static/sentry/app/components/events/contextSummary.jsx:68
+#: static/sentry/app/components/events/contextSummary.jsx:74
+msgid "Unknown User"
+msgstr ""
+
+#: static/sentry/app/components/events/device.jsx:35
+msgid "Device"
+msgstr ""
+
+#: static/sentry/app/components/events/errorItem.jsx:56
+#: static/sentry/app/components/repositoryFileSummary.jsx:83
+msgid "Collapse"
+msgstr ""
+
+#: static/sentry/app/components/events/errorItem.jsx:56
+#: static/sentry/app/views/projectKeys.jsx:161
+msgid "Expand"
+msgstr ""
+
+#: static/sentry/app/components/events/errors.jsx:51
+msgid "Show"
+msgstr ""
+
+#: static/sentry/app/components/events/errors.jsx:51
+msgid "Hide"
+msgstr ""
+
+#: static/sentry/app/components/events/errors.jsx:53
+#, python-format
+msgid "There was %d error encountered while processing this event"
+msgid_plural "There were %d errors encountered while processing this event"
+msgstr[0] ""
+msgstr[1] ""
+
+# this means "formatted" rendering (fancy tables)
+#: static/sentry/app/components/events/eventDataSection.jsx:63
+#: static/sentry/app/components/events/interfaces/request.jsx:77
+msgid "Formatted"
+msgstr ""
+
+#: static/sentry/app/components/events/eventDataSection.jsx:69
+#: static/sentry/app/components/events/interfaces/crashHeader.jsx:134
+#: static/sentry/app/components/events/interfaces/csp.jsx:66
+msgid "Raw"
+msgstr ""
+
+#: static/sentry/app/components/events/eventEntries.jsx:100
+msgid "There was an error rendering this data."
+msgstr ""
+
+#: static/sentry/app/components/events/eventEntries.jsx:133
+#, python-format
+msgid "This event was reported with an old version of the %s SDK."
+msgstr ""
+
+#: static/sentry/app/components/events/eventEntries.jsx:139
+msgid "Learn More"
+msgstr ""
+
+#: static/sentry/app/components/events/eventsPerHour.jsx:107
+msgid "View Stats"
+msgstr ""
+
+#: static/sentry/app/components/events/eventsPerHour.jsx:109
+msgid "Events Per Hour"
+msgstr ""
+
+#: static/sentry/app/components/events/extraData.jsx:40
+msgid "Additional Data"
+msgstr "Addisionele Data"
+
+#: static/sentry/app/components/events/interfaces/breadcrumbs.jsx:153
+msgid "Search breadcrumbs..."
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/breadcrumbs.jsx:81
+msgid "Sorry, no breadcrumbs match your search query."
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/crashHeader.jsx:101
+#: static/sentry/app/views/groupSimilar/similarToolbar.jsx:64
+msgid "Exception"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/crashHeader.jsx:106
+msgid "Toggle stacktrace order"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/crashHeader.jsx:109
+msgid "most recent call last"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/crashHeader.jsx:109
+msgid "most recent call first"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/crashHeader.jsx:121
+msgid "App Only"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/crashHeader.jsx:128
+msgid "Full"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/crashHeader.jsx:56
+msgid "Original"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/crashHeader.jsx:58
+msgid "Symbolicated"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/crashHeader.jsx:64
+msgid "Minified"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/crashHeader.jsx:66
+msgid "Unsymbolicated"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/csp.jsx:60
+msgid "Report"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/csp.jsx:72
+msgid "Help"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/csp.jsx:75
+msgid "CSP Report"
+msgstr ""
+
+#: static/sentry/app/components/events/interfaces/cspHelp.jsx:16
+msgid ""
+"\n"
+"The child-src directive defines the valid sources for web\n"
+"workers and nested browsing contexts loaded using elements such as\n"
+" and