class SentryAppDocs(APIDocsTestCase): def setUp(self): self.org = self.create_organization(owner=self.user, name="Rowdy Tiger") self.project = self.create_project(organization=self.org) self.group = self.create_group(project=self.project) self.sentry_app = self.create_sentry_app( name="Hellboy App", published=True, organization=self.org ) self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org) self.install.save() self.url = reverse( "sentry-api-0-sentry-app-installation-external-issues", kwargs={"uuid": self.install.uuid}, ) self.login_as(user=self.user) def test_post(self): data = { "issueId": self.group.id, "webUrl": "https://somerandom.io/project/issue-id", "project": "ExternalProj", "identifier": "issue-1", } response = self.client.post(self.url, data) request = RequestFactory().post(self.url, data) self.validate_schema(request, response)
class SentryAppInstallationTest(TestCase): def setUp(self): self.user = self.create_user() self.proxy = self.create_user() self.org = self.create_organization() self.application = ApiApplication.objects.create(owner=self.proxy) self.sentry_app = SentryApp.objects.create( application=self.application, name="NullDB", proxy_user=self.proxy, owner=self.org, scope_list=("project:read",), webhook_url="http://example.com", ) self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org) def test_paranoid(self): self.install.save() self.install.delete() assert self.install.date_deleted is not None assert self.install not in SentryAppInstallation.objects.all() def test_date_updated(self): self.install.save() date_updated = self.install.date_updated self.install.save() assert not self.install.date_updated == date_updated def test_related_names(self): self.install.save() assert self.install in self.install.sentry_app.installations.all() assert self.install in self.install.organization.sentry_app_installations.all()
def setUp(self): self.org = self.create_organization(owner=self.user, name="Rowdy Tiger") self.project = self.create_project(organization=self.org) self.group = self.create_group(project=self.project) self.sentry_app = self.create_sentry_app(name="Hellboy App", published=True, organization=self.org) self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org) self.install.save() self.external_issue = self.create_platform_external_issue( group=self.group, service_type=self.sentry_app.slug, display_name="App#issue-1", web_url=self.sentry_app.webhook_url, ) self.url = reverse( "sentry-api-0-sentry-app-installation-external-issue-details", kwargs={ "uuid": self.install.uuid, "external_issue_id": self.external_issue.id }, ) self.login_as(user=self.user)
class SentryAppDetailsDocs(APIDocsTestCase): def setUp(self): self.org = self.create_organization(owner=self.user, name="Rowdy Tiger") self.project = self.create_project(organization=self.org) self.group = self.create_group(project=self.project) self.sentry_app = self.create_sentry_app( name="Hellboy App", published=True, organization=self.org ) self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org) self.install.save() self.external_issue = self.create_platform_external_issue( group=self.group, service_type=self.sentry_app.slug, display_name="App#issue-1", web_url=self.sentry_app.webhook_url, ) self.url = reverse( "sentry-api-0-sentry-app-installation-external-issue-details", kwargs={"uuid": self.install.uuid, "external_issue_id": self.external_issue.id}, ) self.login_as(user=self.user) def test_delete(self): response = self.client.delete(self.url) request = RequestFactory().delete(self.url) self.validate_schema(request, response)
def setUp(self): self.org = self.create_organization(owner=self.user, name="Rowdy Tiger") self.project = self.create_project(organization=self.org) self.group = self.create_group(project=self.project) self.sentry_app = self.create_sentry_app( name="Hellboy App", published=True, organization=self.org ) self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org) self.install.save() self.url = reverse( "sentry-api-0-sentry-app-installation-external-issues", kwargs={"uuid": self.install.uuid}, ) self.login_as(user=self.user)
def get(self, request, organization): try: project = Project.objects.get(id=request.GET["projectId"], organization_id=organization.id) except Project.DoesNotExist: return Response([], status=404) components = [] for install in SentryAppInstallation.get_installed_for_org( organization.id): _components = SentryAppComponent.objects.filter( sentry_app_id=install.sentry_app_id) if "filter" in request.GET: _components = _components.filter(type=request.GET["filter"]) for component in _components: try: sentry_app_components.Preparer.run(component=component, install=install, project=project) components.append(component) except APIError: continue return self.paginate( request=request, queryset=components, paginator_cls=OffsetPaginator, on_results=lambda x: serialize(x, request.user), )
def setUp(self): self.user = self.create_user("*****@*****.**") self.org = self.create_organization(name="Jessla", owner=None) self.create_member(user=self.user, organization=self.org, role="owner") self.sentry_app = self.create_sentry_app( name="Tesla App", published=True, organization=self.org ) self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org) self.install.save() self.login_as(user=self.user) self.url = reverse( "sentry-api-0-sentry-app-installations", kwargs={"organization_slug": self.org.slug}, )
def setUp(self): self.user = self.create_user() self.proxy = self.create_user() self.org = self.create_organization() self.application = ApiApplication.objects.create(owner=self.proxy) self.sentry_app = SentryApp.objects.create( application=self.application, name="NullDB", proxy_user=self.proxy, owner=self.org, scope_list=("project:read",), webhook_url="http://example.com", ) self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org)
def get(self, request: Request, project, rule) -> Response: """ Retrieve a rule Return details on an individual rule. {method} {path} """ # Serialize Rule object serialized_rule = serialize( rule, request.user, RuleSerializer(request.GET.getlist("expand", []))) errors = [] # Prepare Rule Actions that are SentryApp components using the meta fields for action in serialized_rule.get("actions", []): if action.get("_sentry_app_installation") and action.get( "_sentry_app_component"): installation = SentryAppInstallation( **action.get("_sentry_app_installation", {})) component = installation.prepare_ui_component( SentryAppComponent(**action.get("_sentry_app_component")), project, action.get("settings"), ) if component is None: errors.append({ "detail": f"Could not fetch details from {installation.sentry_app.name}" }) action["disabled"] = True continue action["formFields"] = component.schema.get("settings", {}) # Delete meta fields del action["_sentry_app_installation"] del action["_sentry_app_component"] if len(errors): serialized_rule["errors"] = errors return Response(serialized_rule)
def get(self, request: Request, organization, alert_rule) -> Response: """ Fetch an alert rule. `````````````````` :auth: required """ # Serialize Alert Rule serialized_rule = serialize(alert_rule, request.user, DetailedAlertRuleSerializer()) # Prepare AlertRuleTriggerActions that are SentryApp components errors = [] for trigger in serialized_rule.get("triggers", []): for action in trigger.get("actions", []): if action.get("_sentry_app_installation") and action.get("_sentry_app_component"): installation = SentryAppInstallation( **action.get("_sentry_app_installation", {}) ) component = installation.prepare_ui_component( SentryAppComponent(**action.get("_sentry_app_component")), None, action.get("settings"), ) if component is None: errors.append( { "detail": f"Could not fetch details from {installation.sentry_app.name}" } ) action["disabled"] = True continue action["formFields"] = component.schema.get("settings", {}) # Delete meta fields del action["_sentry_app_installation"] del action["_sentry_app_component"] if len(errors): serialized_rule["errors"] = errors return Response(serialized_rule)
class SentryAppInstallationTest(TestCase): def setUp(self): self.user = self.create_user() self.proxy = self.create_user() self.org = self.create_organization() self.application = ApiApplication.objects.create(owner=self.proxy) self.sentry_app = SentryApp.objects.create( application=self.application, name='NullDB', proxy_user=self.proxy, owner=self.user, scope_list=('project:read', ), webhook_url='http://example.com', ) self.install = SentryAppInstallation( sentry_app=self.sentry_app, organization=self.org, ) def test_paranoid(self): self.install.save() self.install.delete() assert self.install.date_deleted is not None assert self.install not in SentryAppInstallation.objects.all() def test_date_updated(self): self.install.save() date_updated = self.install.date_updated self.install.save() assert not self.install.date_updated == date_updated def test_related_names(self): self.install.save() assert self.install in self.install.sentry_app.installations.all() assert self.install in \ self.install.organization.sentry_app_installations.all()
class SentryAppInstallationDocs(APIDocsTestCase): def setUp(self): self.user = self.create_user("*****@*****.**") self.org = self.create_organization(name="Jessla", owner=None) self.create_member(user=self.user, organization=self.org, role="owner") self.sentry_app = self.create_sentry_app( name="Tesla App", published=True, organization=self.org ) self.install = SentryAppInstallation(sentry_app=self.sentry_app, organization=self.org) self.install.save() self.login_as(user=self.user) self.url = reverse( "sentry-api-0-sentry-app-installations", kwargs={"organization_slug": self.org.slug}, ) def test_get(self): response = self.client.get(self.url) request = RequestFactory().get(self.url) self.validate_schema(request, response)
def get_custom_actions(self, project: Project) -> Sequence[Mapping[str, Any]]: action_list = [] for install in SentryAppInstallation.get_installed_for_org( project.organization_id): component = install.prepare_sentry_app_components( "alert-rule-action", project) if component: kwargs = { "install": install, "event_action": self, } action_details = serialize( component, None, SentryAppAlertRuleActionSerializer(), **kwargs) action_list.append(action_details) return action_list
def setUp(self): self.user = self.create_user() self.proxy = self.create_user() self.org = self.create_organization() self.application = ApiApplication.objects.create(owner=self.proxy) self.sentry_app = SentryApp.objects.create( application=self.application, name='NullDB', proxy_user=self.proxy, owner=self.user, scope_list=('project:read', ), webhook_url='http://example.com', ) self.install = SentryAppInstallation( sentry_app=self.sentry_app, organization=self.org, )
def get(self, request, organization): """ Fetches actions that an alert rule can perform for an organization """ if not features.has( "organizations:incidents", organization, actor=request.user): raise ResourceDoesNotExist actions = [] # Cache Integration objects in this data structure to save DB calls. provider_integrations = defaultdict(list) for integration in get_available_action_integrations_for_org( organization): provider_integrations[integration.provider].append(integration) for registered_type in AlertRuleTriggerAction.get_registered_types(): # Used cached integrations for each `registered_type` instead of making N calls. if registered_type.integration_provider: actions += [ build_action_response(registered_type, integration=integration, organization=organization) for integration in provider_integrations[ registered_type.integration_provider] ] # Add all alertable SentryApps to the list. elif registered_type.type == AlertRuleTriggerAction.Type.SENTRY_APP: actions += [ build_action_response(registered_type, sentry_app_installation=install) for install in SentryAppInstallation.get_installed_for_org( organization.id).filter(sentry_app__is_alertable=True, ) ] else: actions.append(build_action_response(registered_type)) return Response(actions, status=status.HTTP_200_OK)
def get_custom_actions(self, project: Project) -> Sequence[Mapping[str, Any]]: action_list = [] for install in SentryAppInstallation.get_installed_for_org( project.organization_id): _components = SentryAppComponent.objects.filter( sentry_app_id=install.sentry_app_id, type="alert-rule-action") for component in _components: try: sentry_app_components.Preparer.run(component=component, install=install, project=project) kwargs = { "install": install, "event_action": self, } action_details = serialize( component, None, SentryAppAlertRuleActionSerializer(), **kwargs) action_list.append(action_details) except APIError: continue return action_list
def get(self, request, project): """ Retrieve the list of configuration options for a given project. """ action_list = [] condition_list = [] filter_list = [] project_has_filters = features.has("projects:alert-filters", project) can_create_tickets = features.has( "organizations:integrations-ticket-rules", project.organization) has_percent_condition = features.has( "organizations:issue-percent-filters", project.organization) # TODO: conditions need to be based on actions for rule_type, rule_cls in rules: node = rule_cls(project) # skip over conditions if they are not in the migrated set for a project with alert-filters if project_has_filters and node.id in MIGRATED_CONDITIONS: continue if not can_create_tickets and node.id in TICKET_ACTIONS: continue context = { "id": node.id, "label": node.label, "enabled": node.is_enabled() } if hasattr(node, "prompt"): context["prompt"] = node.prompt if hasattr(node, "form_fields"): context["formFields"] = node.form_fields if node.id in TICKET_ACTIONS: context["actionType"] = "ticket" context["ticketType"] = node.ticket_type context["link"] = node.link # It is possible for a project to have no services. In that scenario we do # not want the front end to render the action as the action does not have # options. if (node.id == "sentry.rules.actions.notify_event_service.NotifyEventServiceAction" and len(node.get_services()) == 0): continue if rule_type.startswith("condition/"): if (has_percent_condition or context["id"] != "sentry.rules.conditions.event_frequency.EventFrequencyPercentCondition" ): condition_list.append(context) elif rule_type.startswith("filter/"): filter_list.append(context) elif rule_type.startswith("action/"): action_list.append(context) for install in SentryAppInstallation.get_installed_for_org( project.organization_id): _components = SentryAppComponent.objects.filter( sentry_app_id=install.sentry_app_id, type="alert-rule-action") for component in _components: try: sentry_app_components.Preparer.run(component=component, install=install, project=project) action_list.append( serialize(component, request.user, SentryAppAlertRuleActionSerializer())) except APIError: continue context = { "actions": action_list, "conditions": condition_list, "filters": filter_list } return Response(context)
def installations_to_notify(organization, event): installations = SentryAppInstallation.get_installed_for_org( organization.id).select_related("sentry_app") return [i for i in installations if event in i.sentry_app.events]
def _process_resource_change(action, sender, instance_id, retryer=None, *args, **kwargs): # The class is serialized as a string when enqueueing the class. model = TYPES[sender] # The Event model has different hooks for the different event types. The sender # determines which type eg. Error and therefore the 'name' eg. error if issubclass(model, Event): if not kwargs.get("instance"): extra = {"sender": sender, "action": action, "event_id": instance_id} logger.info("process_resource_change.event_missing_event", extra=extra) return name = sender.lower() else: # Some resources are named differently than their model. eg. Group vs Issue. # Looks up the human name for the model. Defaults to the model name. name = RESOURCE_RENAMES.get(model.__name__, model.__name__.lower()) # By default, use Celery's `current` but allow a value to be passed for the # bound Task. retryer = retryer or current # We may run into a race condition where this task executes before the # transaction that creates the Group has committed. try: if issubclass(model, Event): # XXX:(Meredith): Passing through the entire event was an intentional choice # to avoid having to query NodeStore again for data we had previously in # post_process. While this is not ideal, changing this will most likely involve # an overhaul of how we do things in post_process, not just this task alone. instance = kwargs.get("instance") else: instance = model.objects.get(id=instance_id) except model.DoesNotExist as e: # Explicitly requeue the task, so we don't report this to Sentry until # we hit the max number of retries. return retryer.retry(exc=e) event = f"{name}.{action}" if event not in VALID_EVENTS: return org = None if isinstance(instance, Group) or isinstance(instance, Event): org = Organization.objects.get_from_cache( id=Project.objects.get_from_cache(id=instance.project_id).organization_id ) installations = filter( lambda i: event in i.sentry_app.events, SentryAppInstallation.get_installed_for_org(org.id).select_related("sentry_app"), ) for installation in installations: data = {} if isinstance(instance, Event): data[name] = _webhook_event_data(instance, instance.group_id, instance.project_id) else: data[name] = serialize(instance) # Trigger a new task for each webhook send_resource_change_webhook.delay(installation_id=installation.id, event=event, data=data)