def test_user_feedback(self): user_foo = self.create_user('*****@*****.**') report = UserReport.objects.create(project=self.project, group=self.group, name='Homer Simpson', email='*****@*****.**') self.project.teams.first().organization.member_set.create( user=user_foo) with self.tasks(): self.plugin.handle_signal( name='user-reports.created', project=self.project, payload={ 'report': serialize(report, AnonymousUser(), ProjectUserReportSerializer()), }, ) assert len(mail.outbox) == 1 msg = mail.outbox[0] assert msg.subject == '[Sentry] {} - New Feedback from Homer Simpson'.format( self.group.qualified_short_id, ) assert msg.to == [self.user.email]
def get(self, request, project): """ List a Project's User Feedback `````````````````````````````` Return a list of user feedback items within this project. :pparam string organization_slug: the slug of the organization. :pparam string project_slug: the slug of the project. :auth: required """ queryset = UserReport.objects.filter( project=project, group__isnull=False, ).select_related('group') status = request.GET.get('status', 'unresolved') if status == 'unresolved': queryset = queryset.filter( group__status=GroupStatus.UNRESOLVED, ) elif status: return Response({'status': 'Invalid status choice'}, status=400) return self.paginate( request=request, queryset=queryset, order_by='-date_added', on_results=lambda x: serialize(x, request.user, ProjectUserReportSerializer()), paginator_cls=DateTimePaginator, )
def post(self, request, project): """ Submit User Feedback ```````````````````` Submit and associate user feedback with an issue. :pparam string organization_slug: the slug of the organization. :pparam string project_slug: the slug of the project. :auth: required :param string event_id: the event ID :param string name: user's name :param string email: user's email address :param string comments: comments supplied by user """ serializer = UserReportSerializer(data=request.DATA) if not serializer.is_valid(): return Response(serializer.errors, status=400) report = serializer.object report.project = project 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 pass else: report.group = Group.objects.get(id=mapping.group_id) try: with transaction.atomic(): report.save() except IntegrityError: # There was a duplicate, so just overwrite the existing # row with the new one. The only way this ever happens is # if someone is messing around with the API, or doing # something wrong with the SDK, but this behavior is # more reasonable than just hard erroring and is more # expected. report = UserReport.objects.get( project=report.project, event_id=report.event_id, ) report.update( name=report.name, email=report.email, comments=report.comments, date_added=timezone.now(), ) return Response( serialize(report, request.user, ProjectUserReportSerializer()))
def notify(self): from django.contrib.auth.models import AnonymousUser from sentry.api.serializers import ( serialize, ProjectUserReportSerializer ) from sentry.tasks.signals import signal signal.delay( name='user-reports.created', project_id=self.project_id, payload={ 'report': serialize(self, AnonymousUser(), ProjectUserReportSerializer()), }, )
def get(self, request, project): queryset = UserReport.objects.filter( project=project, group__isnull=False, ).select_related('group') return self.paginate( request=request, queryset=queryset, order_by='-date_added', on_results=lambda x: serialize(x, request.user, ProjectUserReportSerializer()), paginator_cls=DateTimePaginator, )
def get(self, request, project): """ List a Project's User Feedback `````````````````````````````` Return a list of user feedback items within this project. :pparam string organization_slug: the slug of the organization. :pparam string project_slug: the slug of the project. :auth: required """ # we dont allow read permission with DSNs if isinstance(request.auth, ProjectKey): return self.respond(status=401) try: environment = self._get_environment_from_request( request, project.organization_id, ) except Environment.DoesNotExist: queryset = UserReport.objects.none() else: queryset = UserReport.objects.filter( project=project, group__isnull=False, ).select_related('group') if environment is not None: queryset = queryset.filter(environment=environment, ) status = request.GET.get('status', 'unresolved') if status == 'unresolved': queryset = queryset.filter( group__status=GroupStatus.UNRESOLVED, ) elif status: return self.respond({'status': 'Invalid status choice'}, status=400) return self.paginate( request=request, queryset=queryset, order_by='-date_added', on_results=lambda x: serialize( x, request.user, ProjectUserReportSerializer( environment_func=self._get_environment_func( request, project.organization_id))), paginator_cls=DateTimePaginator, )
def get(self, request, project): """ List a Project's User Feedback `````````````````````````````` Return a list of user feedback items within this project. :pparam string organization_slug: the slug of the organization. :pparam string project_slug: the slug of the project. :auth: required """ queryset = UserReport.objects.filter( project=project, group__isnull=False, ).select_related('group') return self.paginate( request=request, queryset=queryset, order_by='-date_added', on_results=lambda x: serialize(x, request.user, ProjectUserReportSerializer()), paginator_cls=DateTimePaginator, )
def post(self, request, project): """ Submit User Feedback ```````````````````` Submit and associate user feedback with an issue. :pparam string organization_slug: the slug of the organization. :pparam string project_slug: the slug of the project. :auth: required :param string event_id: the event ID :param string name: user's name :param string email: user's email address :param string comments: comments supplied by user """ serializer = UserReportSerializer(data=request.DATA) if not serializer.is_valid(): return Response(serializer.errors, status=400) report = serializer.object report.project = project # TODO(dcramer): we should probably create the user if they dont # exist, and ideally we'd also associate that with the event euser = self.find_event_user(report) if euser and not euser.name and report.name: euser.update(name=report.name) if euser: report.event_user_id = euser.id try: report.group = Group.objects.from_event_id(project, report.event_id) except Group.DoesNotExist: pass try: with transaction.atomic(): report.save() except IntegrityError: # There was a duplicate, so just overwrite the existing # row with the new one. The only way this ever happens is # if someone is messing around with the API, or doing # something wrong with the SDK, but this behavior is # more reasonable than just hard erroring and is more # expected. existing_report = UserReport.objects.get( project=report.project, event_id=report.event_id, ) existing_report.update( name=report.name, email=report.email, comments=report.comments, date_added=timezone.now(), event_user_id=euser.id if euser else None, ) report = existing_report if report.group_id is None: backfill_group.apply_async( kwargs={ 'report_id': report.id, }, countdown=30, ) user_feedback_received.send(project=report.project, group=report.group, sender=self) return Response( serialize(report, request.user, ProjectUserReportSerializer()))
def post(self, request, project): """ Submit User Feedback ```````````````````` Submit and associate user feedback with an issue. Feedback must be received by the server no more than 30 minutes after the event was saved. Additionally, within 5 minutes of submitting feedback it may also be overwritten. This is useful in situations where you may need to retry sending a request due to network failures. If feedback is rejected due to a mutability threshold, a 409 status code will be returned. :pparam string organization_slug: the slug of the organization. :pparam string project_slug: the slug of the project. :auth: required :param string event_id: the event ID :param string name: user's name :param string email: user's email address :param string comments: comments supplied by user """ serializer = UserReportSerializer(data=request.DATA) if not serializer.is_valid(): return self.respond(serializer.errors, status=400) report = serializer.object report.project = project # TODO(dcramer): we should probably create the user if they dont # exist, and ideally we'd also associate that with the event euser = self.find_event_user(report) if euser and not euser.name and report.name: euser.update(name=report.name) if euser: report.event_user_id = euser.id event = Event.objects.filter( project_id=project.id, event_id=report.event_id, ).first() if not event: try: report.group = Group.objects.from_event_id( project, report.event_id) except Group.DoesNotExist: pass else: # if the event is more than 30 minutes old, we dont allow updates # as it might be abusive if event.datetime < timezone.now() - timedelta(minutes=30): return self.respond( {'detail': 'Feedback for this event cannot be modified.'}, status=409) report.environment = event.get_environment() report.group = event.group try: with transaction.atomic(): report.save() except IntegrityError: # There was a duplicate, so just overwrite the existing # row with the new one. The only way this ever happens is # if someone is messing around with the API, or doing # something wrong with the SDK, but this behavior is # more reasonable than just hard erroring and is more # expected. existing_report = UserReport.objects.get( project=report.project, event_id=report.event_id, ) # if the existing report was submitted more than 5 minutes ago, we dont # allow updates as it might be abusive (replay attacks) if existing_report.date_added < timezone.now() - timedelta( minutes=5): return self.respond( {'detail': 'Feedback for this event cannot be modified.'}, status=409) existing_report.update( name=report.name, email=report.email, comments=report.comments, date_added=timezone.now(), event_user_id=euser.id if euser else None, ) report = existing_report else: if report.group: report.notify() user_feedback_received.send(project=report.project, group=report.group, sender=self) return self.respond( serialize( report, request.user, ProjectUserReportSerializer( environment_func=self._get_environment_func( request, project.organization_id))))