def get(self, request, organization, event_id): """ Resolve a Event ID `````````````````` This resolves a event ID to the project slug and internal issue ID and internal event ID. :pparam string organization_slug: the slug of the organization the event ID should be looked up in. :param string event_id: the event ID to look up. :auth: required Return: organizationSlug projectSlug groupId eventId (optional) """ # Largely copied from ProjectGroupIndexEndpoint if len(event_id) != 32: return Response({'detail': 'Event ID must be 32 characters.'}, status=400) project_slugs_by_id = dict( Project.objects.filter(organization=organization).values_list( 'id', 'slug')) try: event = Event.objects.filter( event_id=event_id, project_id__in=project_slugs_by_id.keys())[0] except IndexError: try: event_mapping = EventMapping.objects.filter( event_id=event_id, project_id__in=project_slugs_by_id.keys())[0] except IndexError: raise ResourceDoesNotExist() return Response({ 'organizationSlug': organization.slug, 'projectSlug': project_slugs_by_id[event_mapping.project_id], 'groupId': six.text_type(event_mapping.group_id), }) return Response({ 'organizationSlug': organization.slug, 'projectSlug': project_slugs_by_id[event.project_id], 'groupId': six.text_type(event.group_id), 'eventId': six.text_type(event.id), 'event': serialize( event, request.user, ), })
def get(self, request, organization, short_id): """ Resolve a Short ID `````````````````` This resolves a short ID to the project slug and internal issue ID. :pparam string organization_slug: the slug of the organization the short ID should be looked up in. :pparam string short_id: the short ID to look up. :auth: required """ try: group = Group.objects.by_qualified_short_id(organization.id, short_id) except Group.DoesNotExist: raise ResourceDoesNotExist() return Response( { "organizationSlug": organization.slug, "projectSlug": group.project.slug, "groupId": six.text_type(group.id), "group": serialize(group, request.user), "shortId": group.qualified_short_id, } )
def convert_args(self, request: Request, organization_slug, team_id, *args, **kwargs): args, kwargs = super().convert_args(request, organization_slug) try: kwargs["team"] = self._get_team(kwargs["organization"], team_id) except Team.DoesNotExist: raise ResourceDoesNotExist(detail=SCIM_404_GROUP_RES) return (args, kwargs)
def get(self, request, organization, event_id): """ Resolve a Event ID `````````````````` This resolves a event ID to the project slug and internal issue ID and internal event ID. :pparam string organization_slug: the slug of the organization the event ID should be looked up in. :param string event_id: the event ID to look up. :auth: required """ # Largely copied from ProjectGroupIndexEndpoint if len(event_id) != 32: return Response({"detail": "Event ID must be 32 characters."}, status=400) # Limit to 100req/s if ratelimiter.is_limited( u"api:event-id-lookup:{}".format( md5_text(request.user.id if request.user and request.user. is_authenticated() else "").hexdigest()), limit=100, window=1, ): return Response( { "detail": "You are attempting to use this endpoint too quickly. Limit is 100 requests/second." }, status=429, ) project_slugs_by_id = dict( Project.objects.filter(organization=organization).values_list( "id", "slug")) try: snuba_filter = eventstore.Filter( conditions=[["event.type", "!=", "transaction"]], project_ids=project_slugs_by_id.keys(), event_ids=[event_id], ) event = eventstore.get_events(filter=snuba_filter, limit=1)[0] except IndexError: raise ResourceDoesNotExist() else: return Response({ "organizationSlug": organization.slug, "projectSlug": project_slugs_by_id[event.project_id], "groupId": six.text_type(event.group_id), "eventId": six.text_type(event.event_id), "event": serialize(event, request.user), })
def patch(self, request, organization, team): """ A SCIM Group PATCH request takes a series of operations to perform on a team. It does them sequentially and if any of them fail no operations should go through. The operations are add members, remove members, replace members, and rename team. """ operations = request.data.get("Operations", []) if len(operations) > 100: return Response(SCIM_400_TOO_MANY_PATCH_OPS_ERROR, status=400) try: with transaction.atomic(): for operation in operations: op = operation["op"].lower() if op == TeamPatchOps.ADD and operation[ "path"] == "members": self._add_members_operation(request, operation, team) elif op == TeamPatchOps.REMOVE and "members" in operation[ "path"]: # the members op contains a filter string like so: # members[userName eq "*****@*****.**"] self._remove_members_operation(request, operation, team) elif op == TeamPatchOps.REPLACE: path = operation.get("path") if path == "members": # delete all the current team members # and replace with the ones in the operation list with transaction.atomic(): queryset = OrganizationMemberTeam.objects.filter( team_id=team.id) queryset.delete() self._add_members_operation( request, operation, team) # azure and okta handle team name change operation differently elif path is None: # for okta self._rename_team_operation( request, operation["value"]["displayName"], team) elif path == "displayName": # for azure self._rename_team_operation( request, operation["value"], team) else: return Response(SCIM_400_UNSUPPORTED_ATTRIBUTE, status=400) except OrganizationMember.DoesNotExist: raise ResourceDoesNotExist(detail=SCIM_404_USER_RES) except IntegrityError as e: sentry_sdk.capture_exception(e) return Response(SCIM_400_INTEGRITY_ERROR, status=400) context = serialize(team, serializer=TeamSCIMSerializer()) return Response(context)
def post(self, request: Request, organization) -> Response: if not self.has_feature(request, organization): return Response(status=404) logger.info("discover1.request", extra={"organization_id": organization.id}) try: requested_projects = set(map(int, request.data.get("projects", []))) except (ValueError, TypeError): raise ResourceDoesNotExist() projects = self._get_projects_by_id(requested_projects, request, organization) serializer = DiscoverQuerySerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=400) serialized = serializer.validated_data has_aggregations = len(serialized.get("aggregations")) > 0 selected_columns = ( serialized.get("conditionFields", []) + [] if has_aggregations else serialized.get("fields", []) ) projects_map = {} for project in projects: projects_map[project.id] = project.slug # Make sure that all selected fields are in the group by clause if there # are aggregations groupby = serialized.get("groupby") or [] fields = serialized.get("fields") or [] if has_aggregations: for field in fields: if field not in groupby: groupby.append(field) return self.do_query( projects=projects_map, start=serialized.get("start"), end=serialized.get("end"), groupby=groupby, selected_columns=selected_columns, conditions=serialized.get("conditions"), orderby=serialized.get("orderby"), limit=serialized.get("limit"), aggregations=serialized.get("aggregations"), rollup=serialized.get("rollup"), filter_keys={"project.id": list(projects_map.keys())}, arrayjoin=serialized.get("arrayjoin"), request=request, turbo=serialized.get("turbo"), )
def get(self, request: Request, organization, metric_name) -> Response: if not features.has( "organizations:metrics", organization, actor=request.user): return Response(status=404) projects = self.get_projects(request, organization) try: metric = get_single_metric_info(projects, metric_name) except InvalidParams: raise ResourceDoesNotExist(detail=f"metric '{metric_name}'") return Response(metric, status=200)
def get(self, request, project, metric_name): if not features.has("organizations:metrics", project.organization, actor=request.user): return Response(status=404) try: metric = DATA_SOURCE.get_single_metric(project, metric_name) except InvalidParams: raise ResourceDoesNotExist(detail=f"metric '{metric_name}'") return Response(metric, status=200)
def get(self, request: Request, project, event_id) -> Response: """ Returns the grouping information for an event ````````````````````````````````````````````` This endpoint returns a JSON dump of the metadata that went into the grouping algorithm. """ event = eventstore.get_event_by_id(project.id, event_id) if event is None: raise ResourceDoesNotExist rv = {} config_name = request.GET.get("config") or None # We always fetch the stored hashes here. The reason for this is # that we want to show in the UI if the forced grouping algorithm # produced hashes that would normally also appear in the event. hashes = event.get_hashes() try: variants = event.get_grouping_variants(force_config=config_name, normalize_stacktraces=True) except GroupingConfigNotFound: raise ResourceDoesNotExist(detail="Unknown grouping config") for (key, variant) in variants.items(): d = variant.as_dict() # Since the hashes are generated on the fly and might no # longer match the stored ones we indicate if the hash # generation caused the hash to mismatch. d["hashMismatch"] = hash_mismatch = ( d["hash"] is not None and d["hash"] not in hashes.hashes and d["hash"] not in hashes.hierarchical_hashes) if hash_mismatch: metrics.incr("event_grouping_info.hash_mismatch") logger.error( "event_grouping_info.hash_mismatch", extra={ "project_id": project.id, "event_id": event_id }, ) else: metrics.incr("event_grouping_info.hash_match") d["key"] = key rv[key] = d return HttpResponse(json.dumps(rv), content_type="application/json")
def get(self, request: Request, organization, metric_name) -> Response: if not features.has( "organizations:metrics", organization, actor=request.user): return Response(status=404) projects = self.get_projects(request, organization) try: metric = get_single_metric_info(projects, metric_name) except InvalidParams as e: raise ResourceDoesNotExist(e) except DerivedMetricParseException as exc: raise ParseError(detail=str(exc)) return Response(metric, status=200)
def get(self, request, project, event_id): """ Returns the grouping information for an event ````````````````````````````````````````````` This endpoint returns a JSON dump of the metadata that went into the grouping algorithm. """ use_snuba = options.get('snuba.events-queries.enabled') event_cls = event_cls = SnubaEvent if use_snuba else Event event = event_cls.objects.from_event_id(event_id, project_id=project.id) if event is None: raise ResourceDoesNotExist Event.objects.bind_nodes([event], 'data') rv = {} config_name = request.GET.get('config') or None # We always fetch the stored hashes here. The reason for this is # that we want to show in the UI if the forced grouping algorithm # produced hashes that would normally also appear in the event. hashes = event.get_hashes() try: variants = event.get_grouping_variants(force_config=config_name, normalize_stacktraces=True) except GroupingConfigNotFound: raise ResourceDoesNotExist(detail='Unknown grouping config') for (key, variant) in six.iteritems(variants): d = variant.as_dict() # Since the hashes are generated on the fly and might no # longer match the stored ones we indicate if the hash # generation caused the hash to mismatch. d['hashMismatch'] = d['hash'] is not None and d[ 'hash'] not in hashes d['key'] = key rv[key] = d return HttpResponse(json.dumps(rv), content_type='application/json')
def get(self, request, organization, event_id): """ Resolve a Event ID `````````````````` This resolves a event ID to the project slug and internal issue ID and internal event ID. :pparam string organization_slug: the slug of the organization the event ID should be looked up in. :param string event_id: the event ID to look up. :auth: required """ # Largely copied from ProjectGroupIndexEndpoint if len(event_id) != 32: return Response({"detail": "Event ID must be 32 characters."}, status=400) project_slugs_by_id = dict( Project.objects.filter(organization=organization).values_list( "id", "slug")) try: event = eventstore.get_events( filter_keys={ "project_id": project_slugs_by_id.keys(), "event_id": event_id }, limit=1, )[0] except IndexError: raise ResourceDoesNotExist() else: return Response({ "organizationSlug": organization.slug, "projectSlug": project_slugs_by_id[event.project_id], "groupId": six.text_type(event.group_id), "eventId": six.text_type(event.id), "event": serialize(event, request.user), })
def get(self, request: Request, organization, event_id) -> Response: """ Resolve an Event ID `````````````````` This resolves an event ID to the project slug and internal issue ID and internal event ID. :pparam string organization_slug: the slug of the organization the event ID should be looked up in. :param string event_id: the event ID to look up. validated by a regex in the URL. :auth: required """ if event_id and not is_event_id(event_id): return Response({"detail": INVALID_ID_DETAILS.format("Event ID")}, status=400) project_slugs_by_id = dict( Project.objects.filter(organization=organization).values_list( "id", "slug")) try: snuba_filter = eventstore.Filter( conditions=[["event.type", "!=", "transaction"]], project_ids=list(project_slugs_by_id.keys()), event_ids=[event_id], ) event = eventstore.get_events(filter=snuba_filter, limit=1)[0] except IndexError: raise ResourceDoesNotExist() else: return Response({ "organizationSlug": organization.slug, "projectSlug": project_slugs_by_id[event.project_id], "groupId": str(event.group_id), "eventId": str(event.event_id), "event": serialize(event, request.user), })
def get(self, request: Request, organization, tag_name) -> Response: if not features.has( "organizations:metrics", organization, actor=request.user): return Response(status=404) metric_names = request.GET.getlist("metric") or None projects = self.get_projects(request, organization) try: tag_values = get_tag_values(projects, tag_name, metric_names) except (InvalidParams, DerivedMetricParseException) as exc: msg = str(exc) # TODO: Use separate error type once we have real data if "Unknown tag" in msg: raise ResourceDoesNotExist(f"tag '{tag_name}'") else: raise ParseError(msg) return Response(tag_values, status=200)
def get(self, request, project, tag_name): if not features.has("organizations:metrics", project.organization, actor=request.user): return Response(status=404) metric_names = request.GET.getlist("metric") or None try: tag_values = DATA_SOURCE.get_tag_values(project, tag_name, metric_names) except InvalidParams as exc: msg = str(exc) # TODO: Use separate error type once we have real data if "Unknown tag" in msg: raise ResourceDoesNotExist(f"tag '{tag_name}'") else: raise ParseError(msg) return Response(tag_values, status=200)
def get(self, request, organization, short_id): """ Resolve a Short ID `````````````````` This resolves a short ID to the project slug and internal issue ID. :pparam string organization_slug: the slug of the organization the short ID should be looked up in. :pparam string short_id: the short ID to look up. :auth: required """ try: group = Group.objects.by_qualified_short_id(organization, short_id) except Group.DoesNotExist: raise ResourceDoesNotExist() return Response({ 'organizationSlug': organization.slug, 'projectSlug': group.project.slug, 'groupId': str(group.id), 'shortId': group.qualified_short_id, })
def get(self, request, organization, work_batch_type): if work_batch_type not in self.app.plugins.handlers_mapped_by_work_batch_type: raise ResourceDoesNotExist() ret = self.app.plugins.handlers_mapped_by_work_batch_type[ work_batch_type] return Response(serialize(ret))
def patch(self, request: Request, organization, team): """ A SCIM Group PATCH request takes a series of operations to perform on a team. It does them sequentially and if any of them fail no operations should go through. The operations are add members, remove members, replace members, and rename team. Update a team's attributes with a SCIM Group PATCH Request. Valid Operations are: * Renaming a team: ```json { "op": "replace", "value": { "id": 23, "displayName": "newName" } } ``` * Adding a member to a team: ```json { "op": "add", "path": "members", "value": [ { "value": 23, "display": "*****@*****.**" } ] } ``` * Removing a member from a team: ```json { "op": "remove", "path": "members[value eq \"23\"]" } ``` * Replacing an entire member set of a team: ```json { "op": "replace", "path": "members", "value": [ { "value": 23, "display": "*****@*****.**" }, { "value": 24, "display": "*****@*****.**" } ] } ``` """ operations = request.data.get("Operations", []) if len(operations) > 100: return Response(SCIM_400_TOO_MANY_PATCH_OPS_ERROR, status=400) try: with transaction.atomic(): for operation in operations: op = operation["op"].lower() if op == TeamPatchOps.ADD and operation["path"] == "members": self._add_members_operation(request, operation, team) elif op == TeamPatchOps.REMOVE and "members" in operation["path"]: self._remove_members_operation( request, self._get_member_id_for_remove_op(operation), team ) elif op == TeamPatchOps.REPLACE: path = operation.get("path") if path == "members": # delete all the current team members # and replace with the ones in the operation list with transaction.atomic(): queryset = OrganizationMemberTeam.objects.filter(team_id=team.id) queryset.delete() self._add_members_operation(request, operation, team) # azure and okta handle team name change operation differently elif path is None: # for okta self._rename_team_operation( request, operation["value"]["displayName"], team ) elif path == "displayName": # for azure self._rename_team_operation(request, operation["value"], team) else: return Response(SCIM_400_UNSUPPORTED_ATTRIBUTE, status=400) except OrganizationMember.DoesNotExist: raise ResourceDoesNotExist(detail=SCIM_404_USER_RES) except IntegrityError as e: sentry_sdk.capture_exception(e) return Response(SCIM_400_INTEGRITY_ERROR, status=400) return self.respond(status=204)