def post_process(self, event, **kwargs): rl_key = u"{}:{}".format(self.conf_key, event.project.organization_id) # limit segment to 50 requests/second limit, window = self.get_rate_limit() if limit and window and ratelimits.is_limited( rl_key, limit=limit, window=window): logger.info( "data_forwarding.skip_rate_limited", extra={ "event_id": event.event_id, "issue_id": event.group_id, "project_id": event.project_id, "organization_id": event.project.organization_id, }, ) return payload = self.get_event_payload(event) success = self.forward_event(event, payload) if success is False: # TODO(dcramer): record failure pass tsdb.incr(tsdb.models.project_total_forwarded, event.project.id, count=1)
def post(self, request, project, helper, **kwargs): data = helper.safely_load_json_string(request.body) # Do origin check based on the `document-uri` key as explained # in `_dispatch`. try: report = data['csp-report'] except KeyError: raise APIError('Missing csp-report') origin = report.get('document-uri') # No idea, but this is garbage if origin == 'about:blank': raise APIForbidden('Invalid document-uri') if not is_valid_origin(origin, project): if project: tsdb.incr(tsdb.models.project_total_received_cors, project.id) raise APIForbidden('Invalid document-uri') # Attach on collected meta data. This data obviously isn't a part # of the spec, but we need to append to the report sentry specific things. report['_meta'] = { 'release': request.GET.get('sentry_release'), } response_or_event_id = self.process(request, project=project, helper=helper, data=report, **kwargs) if isinstance(response_or_event_id, HttpResponse): return response_or_event_id return HttpResponse(status=201)
def process_service_hook(servicehook_id, event, **kwargs): from sentry import tsdb from sentry.models import ServiceHook try: servicehook = ServiceHook.objects.get(id=servicehook_id) except ServiceHook.DoesNotExist: return tsdb.incr(tsdb.models.servicehook_fired, servicehook.id) if servicehook.version == 0: payload = get_payload_v0(event) else: raise NotImplementedError body = json.dumps(payload) headers = { 'Content-Type': 'application/json', 'X-ServiceHook-Timestamp': int(time()), 'X-ServiceHook-GUID': servicehook.guid, 'X-ServiceHook-Signature': servicehook.build_signature(body), } safe_urlopen( url=servicehook.url, body=body, headers=headers, timeout=5, verify_ssl=False, )
def post(self, request, project, helper, **kwargs): json_body = helper.safely_load_json_string(request.body) report_type = self.security_report_type(json_body) if report_type is None: raise APIError('Unrecognized security report type') interface = get_interface(report_type) try: instance = interface.from_raw(json_body) except jsonschema.ValidationError as e: raise APIError('Invalid security report: %s' % str(e).splitlines()[0]) # Do origin check based on the `document-uri` key as explained in `_dispatch`. origin = instance.get_origin() if not is_valid_origin(origin, project): if project: tsdb.incr(tsdb.models.project_total_received_cors, project.id) raise APIForbidden('Invalid origin') data = { 'interface': interface.path, 'report': instance, 'release': request.GET.get('sentry_release'), 'environment': request.GET.get('sentry_environment'), } response_or_event_id = self.process( request, project=project, helper=helper, data=data, **kwargs ) if isinstance(response_or_event_id, HttpResponse): return response_or_event_id return HttpResponse(content_type='application/javascript', status=201)
def post(self, request, project, helper, **kwargs): json_body = safely_load_json_string(request.body) report_type = self.security_report_type(json_body) if report_type is None: raise APIError('Unrecognized security report type') interface = get_interface(report_type) try: instance = interface.from_raw(json_body) except jsonschema.ValidationError as e: raise APIError('Invalid security report: %s' % str(e).splitlines()[0]) # Do origin check based on the `document-uri` key as explained in `_dispatch`. origin = instance.get_origin() if not is_valid_origin(origin, project): if project: tsdb.incr(tsdb.models.project_total_received_cors, project.id) raise APIForbidden('Invalid origin') data = { 'interface': interface.path, 'report': instance, 'release': request.GET.get('sentry_release'), 'environment': request.GET.get('sentry_environment'), } response_or_event_id = self.process(request, project=project, helper=helper, data=data, **kwargs) if isinstance(response_or_event_id, HttpResponse): return response_or_event_id return HttpResponse(content_type='application/javascript', status=201)
def post(self, request, project, helper, **kwargs): data = helper.safely_load_json_string(request.body) # Do origin check based on the `document-uri` key as explained # in `_dispatch`. try: report = data['csp-report'] except KeyError: raise APIError('Missing csp-report') origin = report.get('document-uri') # No idea, but this is garbage if origin == 'about:blank': raise APIForbidden('Invalid document-uri') if not is_valid_origin(origin, project): if project: tsdb.incr(tsdb.models.project_total_received_cors, project.id) raise APIForbidden('Invalid document-uri') # Attach on collected meta data. This data obviously isn't a part # of the spec, but we need to append to the report sentry specific things. report['_meta'] = { 'release': request.GET.get('sentry_release'), } response_or_event_id = self.process( request, project=project, helper=helper, data=report, **kwargs ) if isinstance(response_or_event_id, HttpResponse): return response_or_event_id return HttpResponse(status=201)
def process_service_hook(servicehook_id, event, **kwargs): from sentry import tsdb from sentry.models import ServiceHook try: servicehook = ServiceHook.objects.get(id=servicehook_id) except ServiceHook.DoesNotExist: return tsdb.incr(tsdb.models.servicehook_fired, servicehook.id) if servicehook.version == 0: payload = get_payload_v0(event) else: raise NotImplementedError body = json.dumps(payload) headers = { 'Content-Type': 'application/json', 'X-ServiceHook-Timestamp': six.text_type(int(time())), 'X-ServiceHook-GUID': servicehook.guid, 'X-ServiceHook-Signature': servicehook.build_signature(body), } safe_urlopen( url=servicehook.url, data=body, headers=headers, timeout=5, verify_ssl=False, )
def process_service_hook(servicehook_id, event, **kwargs): try: servicehook = ServiceHook.objects.get(id=servicehook_id) except ServiceHook.DoesNotExist: return if servicehook.version == 0: payload = get_payload_v0(event) else: raise NotImplementedError from sentry import tsdb tsdb.incr(tsdb.models.servicehook_fired, servicehook.id) headers = { "Content-Type": "application/json", "X-ServiceHook-Timestamp": str(int(time())), "X-ServiceHook-GUID": servicehook.guid, "X-ServiceHook-Signature": servicehook.build_signature(json.dumps(payload)), } safe_urlopen( url=servicehook.url, data=json.dumps(payload), headers=headers, timeout=5, verify_ssl=False )
def _dispatch(self, request, helper, project_id=None, origin=None, *args, **kwargs): request.user = AnonymousUser() project = self._get_project_from_id(project_id) if project: helper.context.bind_project(project) Raven.tags_context(helper.context.get_tags_context()) if origin is not None: # This check is specific for clients who need CORS support if not project: raise APIError('Client must be upgraded for CORS support') if not is_valid_origin(origin, project): tsdb.incr(tsdb.models.project_total_received_cors, project.id) raise APIForbidden('Invalid origin: %s' % (origin, )) # XXX: It seems that the OPTIONS call does not always include custom headers if request.method == 'OPTIONS': response = self.options(request, project) else: auth = self._parse_header(request, helper, project) key = helper.project_key_from_auth(auth) # Legacy API was /api/store/ and the project ID was only available elsewhere if not project: project = Project.objects.get_from_cache(id=key.project_id) helper.context.bind_project(project) elif key.project_id != project.id: raise APIError('Two different projects were specified') helper.context.bind_auth(auth) Raven.tags_context(helper.context.get_tags_context()) # Explicitly bind Organization so we don't implicitly query it later # this just allows us to comfortably assure that `project.organization` is safe. # This also allows us to pull the object from cache, instead of being # implicitly fetched from database. project.organization = Organization.objects.get_from_cache( id=project.organization_id) response = super(APIView, self).dispatch( request=request, project=project, auth=auth, helper=helper, key=key, **kwargs ) if origin: if origin == 'null': # If an Origin is `null`, but we got this far, that means # we've gotten past our CORS check for some reason. But the # problem is that we can't return "null" as a valid response # to `Access-Control-Allow-Origin` and we don't have another # value to work with, so just allow '*' since they've gotten # this far. response['Access-Control-Allow-Origin'] = '*' else: response['Access-Control-Allow-Origin'] = origin return response
def increment(self, event, count, environment_id, timestamp=None): tsdb.incr( tsdb.models.group, event.group_id, count=count, environment_id=environment_id, timestamp=timestamp, )
def save_event(cache_key=None, data=None, start_time=None, event_id=None, **kwargs): """ Saves an event to the database. """ from sentry.event_manager import HashDiscarded, EventManager from sentry import tsdb if cache_key: data = default_cache.get(cache_key) if event_id is None and data is not None: event_id = data['event_id'] if data is None: metrics.incr('events.failed', tags={ 'reason': 'cache', 'stage': 'post' }) return project = data.pop('project') delete_raw_event(project, event_id, allow_hint_clear=True) Raven.tags_context({ 'project': project, }) try: manager = EventManager(data) manager.save(project) except HashDiscarded as exc: # TODO(jess): remove this before it goes out to a wider audience info_logger.info('discarded.hash', extra={ 'project_id': project, 'description': exc.message, }) tsdb.incr( tsdb.models.project_total_received_discarded, project, timestamp=to_datetime(start_time) if start_time is not None else None, ) finally: if cache_key: default_cache.delete(cache_key) if start_time: metrics.timing('events.time-to-process', time() - start_time, instance=data['platform'])
def post_process(self, event, **kwargs): if self.is_ratelimited(event): return payload = self.get_event_payload(event) success = self.forward_event(event, payload) if success is False: # TODO(dcramer): record failure pass tsdb.incr(tsdb.models.project_total_forwarded, event.project.id, count=1)
def test_str_key_id(self): tsdb.incr(tsdb.models.key_total_received, self.key.id, count=1) tsdb.incr(tsdb.models.key_total_received, str(self.key.id), count=1) response = self.client.get(self.path) assert response.status_code == 200 assert response.status_code == 200, response.content assert response.data[-1]["total"] == 2, response.data for point in response.data[:-1]: assert point["total"] == 0 assert len(response.data) == 24
def test_rate_reached(self, mock_now): mock_now.return_value = datetime(2016, 8, 1, 0, 0, 0, 0, tzinfo=timezone.utc) snooze = GroupSnooze.objects.create(group=self.group, count=100, window=24 * 60) for n in range(6): tsdb.incr( tsdb.models.group, self.group.id, count=20, timestamp=mock_now() - timedelta(minutes=n), ) assert not snooze.is_valid(test_rates=True)
def test_str_key_id(self): tsdb.incr(tsdb.models.key_total_received, self.key.id, count=1) tsdb.incr(tsdb.models.key_total_received, six.text_type(self.key.id), count=1) response = self.client.get(self.path) assert response.status_code == 200 assert response.status_code == 200, response.content assert response.data[-1]['total'] == 2, response.data for point in response.data[:-1]: assert point['total'] == 0 assert len(response.data) == 24
def test_simple(self): tsdb.incr(tsdb.models.key_total_received, self.key.id, count=3) tsdb.incr(tsdb.models.key_total_blacklisted, self.key.id, count=1) response = self.client.get(self.path) assert response.status_code == 200 assert response.status_code == 200, response.content assert response.data[-1]['total'] == 3, response.data assert response.data[-1]['filtered'] == 1, response.data for point in response.data[:-1]: assert point['total'] == 0 assert len(response.data) == 24
def test_resolution(self): self.login_as(user=self.user) org = self.create_organization(owner=self.user) tsdb.incr(tsdb.models.organization_total_received, org.id, count=3) url = reverse("sentry-api-0-organization-stats", args=[org.slug]) response = self.client.get("{}?resolution=1d".format(url)) assert response.status_code == 200, response.content assert response.data[-1][1] == 3, response.data assert len(response.data) == 1
def test_resolution(self): self.login_as(user=self.user) org = self.create_organization(owner=self.user) tsdb.incr(tsdb.models.organization_total_received, org.id, count=3) url = reverse('sentry-api-0-organization-stats', args=[org.slug]) response = self.client.get('{}?resolution=1d'.format(url)) assert response.status_code == 200, response.content assert response.data[-1][1] == 3, response.data assert len(response.data) == 1
def test_simple(self): self.login_as(user=self.user) org = self.create_organization(owner=self.user) tsdb.incr(tsdb.models.organization_total_received, org.id, count=3) url = reverse('sentry-api-0-organization-stats', args=[org.slug]) response = self.client.get(url) assert response.status_code == 200, response.content assert response.data[-1][1] == 3, response.data for point in response.data[:-1]: assert point[1] == 0 assert len(response.data) == 24
def _incr_internal(key, instance=None, tags=None, amount=1): from sentry import tsdb if _should_sample(): amount = _sampled_value(amount) if instance: full_key = '{}.{}'.format(key, instance) else: full_key = key try: tsdb.incr(tsdb.models.internal, full_key, count=amount) except Exception: logger = logging.getLogger('sentry.errors') logger.exception('Unable to incr internal metric')
def test_simple(self): self.login_as(user=self.user) org = self.create_organization(owner=self.user) tsdb.incr(tsdb.models.organization_total_received, org.id, count=3) url = reverse("sentry-api-0-organization-stats", args=[org.slug]) response = self.client.get(url) assert response.status_code == 200, response.content assert response.data[-1][1] == 3, response.data for point in response.data[:-1]: assert point[1] == 0 assert len(response.data) == 24
def test_rate_reached(self, mock_now): mock_now.return_value = datetime(2016, 8, 1, 0, 0, 0, 0, tzinfo=timezone.utc) snooze = GroupSnooze.objects.create( group=self.group, count=100, window=24 * 60, ) for n in range(6): tsdb.incr( tsdb.models.group, self.group.id, count=20, timestamp=mock_now() - timedelta(minutes=n), ) assert not snooze.is_valid(test_rates=True)
def post_process(self, event, **kwargs): rl_key = '{}:{}'.format( self.conf_key, event.project.organization_id, ) # limit segment to 50 requests/second limit, window = self.get_rate_limit() if limit and window and ratelimits.is_limited(rl_key, limit=limit, window=window): return payload = self.get_event_payload(event) success = self.forward_event(event, payload) if success is False: # TODO(dcramer): record failure pass tsdb.incr(tsdb.models.project_total_forwarded, event.project.id, count=1)
def worker(): from sentry import tsdb while True: key, instance, tags, amount, sample_rate = q.get() amount = _sampled_value(amount, sample_rate) if instance: full_key = u"{}.{}".format(key, instance) else: full_key = key try: tsdb.incr(tsdb.models.internal, full_key, count=amount) except Exception: logger = logging.getLogger("sentry.errors") logger.exception("Unable to incr internal metric") finally: q.task_done()
def worker(): from sentry import tsdb while True: key, instance, tags, amount = q.get() amount = _sampled_value(amount) if instance: full_key = u'{}.{}'.format(key, instance) else: full_key = key try: tsdb.incr(tsdb.models.internal, full_key, count=amount) except Exception: logger = logging.getLogger('sentry.errors') logger.exception('Unable to incr internal metric') finally: q.task_done()
def save_event(cache_key=None, data=None, start_time=None, event_id=None, **kwargs): """ Saves an event to the database. """ from sentry.event_manager import HashDiscarded, EventManager from sentry import tsdb if cache_key: data = default_cache.get(cache_key) if event_id is None and data is not None: event_id = data['event_id'] if data is None: metrics.incr('events.failed', tags={'reason': 'cache', 'stage': 'post'}) return project = data.pop('project') delete_raw_event(project, event_id, allow_hint_clear=True) Raven.tags_context({ 'project': project, }) try: manager = EventManager(data) manager.save(project) except HashDiscarded as exc: # TODO(jess): remove this before it goes out to a wider audience info_logger.info( 'discarded.hash', extra={ 'project_id': project, 'description': exc.message, } ) tsdb.incr(tsdb.models.project_total_received_discarded, project, timestamp=start_time) finally: if cache_key: default_cache.delete(cache_key) if start_time: metrics.timing( 'events.time-to-process', time() - start_time, instance=data['platform'])
def send_request(servicehook, payload, verify_ssl=None): from sentry import tsdb tsdb.incr(tsdb.models.servicehook_fired, servicehook.id) headers = { 'Content-Type': 'application/json', 'X-ServiceHook-Timestamp': six.text_type(int(time())), 'X-ServiceHook-GUID': servicehook.guid, 'X-ServiceHook-Signature': servicehook.build_signature(json.dumps(payload)), } safe_urlopen( url=servicehook.url, data=json.dumps(payload), headers=headers, timeout=5, verify_ssl=(verify_ssl or False), )
def test_simple(self): project = self.create_project() hook = ServiceHook.objects.get_or_create(project_id=project.id, actor_id=self.user.id, url="http://example.com")[0] self.login_as(user=self.user) path = "/api/0/projects/{}/{}/hooks/{}/stats/".format( project.organization.slug, project.slug, hook.guid) tsdb.incr(tsdb.models.servicehook_fired, hook.id, count=3) response = self.client.get(path) assert response.status_code == 200 assert response.status_code == 200, response.content assert response.data[-1]["total"] == 3, response.data for point in response.data[:-1]: assert point["total"] == 0 assert len(response.data) == 24
def test_simple(self): self.login_as(user=self.user) project1 = self.create_project(name='foo') project2 = self.create_project(name='bar') tsdb.incr(tsdb.models.project_total_received, project1.id, count=3) tsdb.incr(tsdb.models.project_total_received, project2.id, count=5) url = reverse('sentry-api-0-project-stats', kwargs={ 'organization_slug': project1.organization.slug, 'project_slug': project1.slug, }) response = self.client.get(url, format='json') assert response.status_code == 200, response.content assert response.data[-1][1] == 3, response.data for point in response.data[:-1]: assert point[1] == 0 assert len(response.data) == 24
def post(self, request, sentry_app): """ Increment a TSDB metric relating to Sentry App interactions :param string tsdbField the name of the TSDB model to increment :param string componentType required for 'sentry_app_component_interacted' metric """ # Request should have identifier field stored in TSDBModel tsdb_field = request.data.get("tsdbField", "") model = getattr(tsdb.models, tsdb_field, None) if model is None or model not in TSDB_MODELS: return Response( { "detail": "The tsdbField must be one of: sentry_app_viewed, sentry_app_component_interacted" }, status=400, ) if model == tsdb.models.sentry_app_component_interacted: component_type = request.data.get("componentType", None) if component_type is None or component_type not in COMPONENT_TYPES: return Response( { "detail": "The field componentType is required and must be one of %s" % (COMPONENT_TYPES) }, status=400, ) key = get_component_interaction_key(sentry_app, request.data["componentType"]) elif model == tsdb.models.sentry_app_viewed: key = sentry_app.id # Timestamp is automatically created tsdb.incr(model, key) return Response({}, status=201)
def test_simple(self): self.login_as(user=self.user) team = self.create_team(members=[self.user]) project_1 = self.create_project(teams=[team], name='a') project_2 = self.create_project(teams=[team], name='b') team_2 = self.create_team(members=[self.user]) project_3 = self.create_project(teams=[team_2], name='c') tsdb.incr(tsdb.models.project, project_1.id, count=3) tsdb.incr(tsdb.models.project, project_2.id, count=5) tsdb.incr(tsdb.models.project, project_3.id, count=10) url = reverse( 'sentry-api-0-team-stats', kwargs={ 'organization_slug': team.organization.slug, 'team_slug': team.slug, } ) response = self.client.get(url) assert response.status_code == 200, response.content assert response.data[-1][1] == 8, response.data for point in response.data[:-1]: assert point[1] == 0 assert len(response.data) == 24
def test_simple(self): self.login_as(user=self.user) group1 = self.create_group() group2 = self.create_group() url = '/api/0/issues/{}/stats/'.format(group1.id) response = self.client.get(url, format='json') assert response.status_code == 200, response.content for point in response.data: assert point[1] == 0 assert len(response.data) == 24 tsdb.incr(tsdb.models.group, group1.id, count=3) tsdb.incr(tsdb.models.group, group2.id, count=5) response = self.client.get(url, format='json') assert response.status_code == 200, response.content assert response.data[-1][1] == 3, response.data for point in response.data[:-1]: assert point[1] == 0 assert len(response.data) == 24
def test_simple(self): project = self.create_project() hook = ServiceHook.objects.get_or_create( project_id=project.id, actor_id=self.user.id, url='http://example.com', )[0] self.login_as(user=self.user) path = '/api/0/projects/{}/{}/hooks/{}/stats/'.format( project.organization.slug, project.slug, hook.guid, ) tsdb.incr(tsdb.models.servicehook_fired, hook.id, count=3) response = self.client.get(path) assert response.status_code == 200 assert response.status_code == 200, response.content assert response.data[-1]['total'] == 3, response.data for point in response.data[:-1]: assert point['total'] == 0 assert len(response.data) == 24
def test_simple(self): self.login_as(user=self.user) group1 = self.create_group() group2 = self.create_group() url = u'/api/0/issues/{}/stats/'.format(group1.id) response = self.client.get(url, format='json') assert response.status_code == 200, response.content for point in response.data: assert point[1] == 0 assert len(response.data) == 24 tsdb.incr(tsdb.models.group, group1.id, count=3) tsdb.incr(tsdb.models.group, group2.id, count=5) response = self.client.get(url, format='json') assert response.status_code == 200, response.content assert response.data[-1][1] == 3, response.data for point in response.data[:-1]: assert point[1] == 0 assert len(response.data) == 24
def test_get_error_message_stats(self): self.login_as(user=self.user) project1 = self.create_project(name="foo") STAT_OPTS = { "ip-address": 1, "release-version": 2, "error-message": 3, "browser-extensions": 4, "legacy-browsers": 5, "localhost": 6, "web-crawlers": 7, "invalid-csp": 8, } tsdb.incr( tsdb.models.project_total_received_ip_address, project1.id, count=STAT_OPTS["ip-address"], ) tsdb.incr( tsdb.models.project_total_received_release_version, project1.id, count=STAT_OPTS["release-version"], ) tsdb.incr( tsdb.models.project_total_received_error_message, project1.id, count=STAT_OPTS["error-message"], ) tsdb.incr( tsdb.models.project_total_received_browser_extensions, project1.id, count=STAT_OPTS["browser-extensions"], ) tsdb.incr( tsdb.models.project_total_received_legacy_browsers, project1.id, count=STAT_OPTS["legacy-browsers"], ) tsdb.incr(tsdb.models.project_total_received_localhost, project1.id, count=STAT_OPTS["localhost"]) tsdb.incr( tsdb.models.project_total_received_web_crawlers, project1.id, count=STAT_OPTS["web-crawlers"], ) tsdb.incr( tsdb.models.project_total_received_invalid_csp, project1.id, count=STAT_OPTS["invalid-csp"], ) url = reverse( "sentry-api-0-project-stats", kwargs={ "organization_slug": project1.organization.slug, "project_slug": project1.slug }, ) for stat in STAT_OPTS.keys(): response = self.client.get(url, {"stat": stat}, format="json") assert response.status_code == 200, response.content assert len(response.data) == 24 assert response.data[-1][1] == STAT_OPTS[stat], response.data
def save_event(cache_key=None, data=None, start_time=None, event_id=None, **kwargs): """ Saves an event to the database. """ from sentry.event_manager import HashDiscarded, EventManager from sentry import quotas, tsdb from sentry.models import ProjectKey if cache_key: data = default_cache.get(cache_key) if event_id is None and data is not None: event_id = data['event_id'] if data is None: metrics.incr('events.failed', tags={ 'reason': 'cache', 'stage': 'post' }) return project_id = data.pop('project') delete_raw_event(project_id, event_id, allow_hint_clear=True) Raven.tags_context({ 'project': project_id, }) try: manager = EventManager(data) manager.save(project_id) except HashDiscarded: tsdb.incr( tsdb.models.project_total_received_discarded, project_id, timestamp=to_datetime(start_time) if start_time is not None else None, ) try: project = Project.objects.get_from_cache(id=project_id) except Project.DoesNotExist: pass else: project_key = None if data.get('key_id') is not None: try: project_key = ProjectKey.objects.get_from_cache( id=data['key_id']) except ProjectKey.DoesNotExist: pass quotas.refund( project, key=project_key, timestamp=start_time, ) finally: if cache_key: default_cache.delete(cache_key) if start_time: metrics.timing('events.time-to-process', time() - start_time, instance=data['platform'])
def test_get_error_message_stats(self): self.login_as(user=self.user) project1 = self.create_project(name='foo') STAT_OPTS = { 'ip-address': 1, 'release-version': 2, 'error-message': 3, 'browser-extensions': 4, 'legacy-browsers': 5, 'localhost': 6, 'web-crawlers': 7, 'invalid-csp': 8, } tsdb.incr( tsdb.models.project_total_received_ip_address, project1.id, count=STAT_OPTS['ip-address'] ) tsdb.incr( tsdb.models.project_total_received_release_version, project1.id, count=STAT_OPTS['release-version'] ) tsdb.incr( tsdb.models.project_total_received_error_message, project1.id, count=STAT_OPTS['error-message'] ) tsdb.incr( tsdb.models.project_total_received_browser_extensions, project1.id, count=STAT_OPTS['browser-extensions'] ) tsdb.incr( tsdb.models.project_total_received_legacy_browsers, project1.id, count=STAT_OPTS['legacy-browsers'] ) tsdb.incr( tsdb.models.project_total_received_localhost, project1.id, count=STAT_OPTS[ 'localhost'] ) tsdb.incr( tsdb.models.project_total_received_web_crawlers, project1.id, count=STAT_OPTS['web-crawlers'] ) tsdb.incr( tsdb.models.project_total_received_invalid_csp, project1.id, count=STAT_OPTS['invalid-csp'] ) url = reverse( 'sentry-api-0-project-stats', kwargs={ 'organization_slug': project1.organization.slug, 'project_slug': project1.slug, } ) for stat in STAT_OPTS.keys(): response = self.client.get(url, {'stat': stat}, format='json') assert response.status_code == 200, response.content assert len(response.data) == 24 assert response.data[-1][1] == STAT_OPTS[stat], response.data