diff --git a/src/sentry/interfaces/contexts.py b/src/sentry/interfaces/contexts.py index 8f844ba9945a67..c35854302df9bc 100644 --- a/src/sentry/interfaces/contexts.py +++ b/src/sentry/interfaces/contexts.py @@ -23,17 +23,38 @@ context_types = {} +class _IndexMapping(object): + + def __init__(self, vars): + self.vars = vars + + def __getitem__(self, key): + if key[:1] == '?': + try: + return self.vars[key[1:]] + except LookupError: + return None + return self.vars[key] + + class _IndexFormatter(string.Formatter): def format_field(self, value, format_spec): - if not format_spec and isinstance(value, bool): - return value and 'yes' or 'no' + if format_spec[:1] == '?': + args = format_spec[1:].split(':', 1) + if value: + return args[0] + if len(args) == 2: + return args[1] + return u'' + if isinstance(value, bool): + return value and u'yes' or u'no' return string.Formatter.format_field(self, value, format_spec) def format_index_expr(format_string, data): return unicode(_IndexFormatter().vformat( - unicode(format_string), (), data).strip()) + unicode(format_string), (), _IndexMapping(data)).strip()) def contexttype(name): @@ -83,8 +104,8 @@ class DefaultContextType(ContextType): @contexttype('device') class DeviceContextType(ContextType): indexed_fields = { - '': u'{model}', - 'family': u'{family}', + '': u'{model}{?simulator:? Simulator:}', + 'family': u'{family}{?simulator:? Simulator:}', } # model_id, arch diff --git a/src/sentry/static/sentry/app/components/events/contexts/device.jsx b/src/sentry/static/sentry/app/components/events/contexts/device.jsx index 5847f491780bc9..9c71d7b11a3382 100644 --- a/src/sentry/static/sentry/app/components/events/contexts/device.jsx +++ b/src/sentry/static/sentry/app/components/events/contexts/device.jsx @@ -11,7 +11,7 @@ const DeviceContextType = React.createClass({ render() { let {name, family, model, model_id, arch, battery_level, orientation, - ...data} = this.props.data; + simulator, ...data} = this.props.data; return ( ); diff --git a/tests/sentry/interfaces/test_contexts.py b/tests/sentry/interfaces/test_contexts.py index 6246f13e5cd2f7..dabb7e787f693e 100644 --- a/tests/sentry/interfaces/test_contexts.py +++ b/tests/sentry/interfaces/test_contexts.py @@ -103,6 +103,64 @@ def test_device_with_alias(self): } } + def test_device_sim(self): + ctx = Contexts.to_python({ + 'device': { + 'name': 'My iPad', + 'model': 'iPad1,2', + 'family': 'iPad', + 'model_id': '1234AB', + 'version': '1.2.3', + 'arch': 'arm64', + 'simulator': True, + }, + }) + assert sorted(ctx.iter_tags()) == [ + ('device', 'iPad1,2 Simulator'), + ('device.family', 'iPad Simulator'), + ] + assert ctx.to_json() == { + 'device': { + 'type': 'device', + 'name': 'My iPad', + 'model': 'iPad1,2', + 'family': 'iPad', + 'model_id': '1234AB', + 'version': '1.2.3', + 'arch': 'arm64', + 'simulator': True, + } + } + + def test_device_sim_explicit_off(self): + ctx = Contexts.to_python({ + 'device': { + 'name': 'My iPad', + 'model': 'iPad1,2', + 'family': 'iPad', + 'model_id': '1234AB', + 'version': '1.2.3', + 'arch': 'arm64', + 'simulator': False, + }, + }) + assert sorted(ctx.iter_tags()) == [ + ('device', 'iPad1,2'), + ('device.family', 'iPad'), + ] + assert ctx.to_json() == { + 'device': { + 'type': 'device', + 'name': 'My iPad', + 'model': 'iPad1,2', + 'family': 'iPad', + 'model_id': '1234AB', + 'version': '1.2.3', + 'arch': 'arm64', + 'simulator': False, + } + } + def test_default(self): ctx = Contexts.to_python({ 'whatever': {