Exemple #1
0
    def _build(
        text: str,
        title: Optional[str] = None,
        footer: Optional[str] = None,
        color: Optional[str] = None,
        **kwargs: Any,
    ) -> SlackAttachment:
        """
        Helper to DRY up Slack specific fields.

        :param string text: Body text.
        :param [string] title: Title text.
        :param [string] footer: Footer text.
        :param [string] color: The key in the Slack palate table, NOT hex. Default: "info".
        :param kwargs: Everything else.
        """
        # If `footer` string is passed, automatically attach a `footer_icon`.
        if footer:
            kwargs["footer"] = footer
            kwargs["footer_icon"] = str(
                absolute_uri(
                    get_asset_url("sentry", "images/sentry-email-avatar.png")))

        if title:
            kwargs["title"] = title

        return {
            "text": text,
            "mrkdwn_in": ["text"],
            "color": LEVEL_TO_COLOR[color or "info"],
            **kwargs,
        }
Exemple #2
0
def locale_js_include(context):
    """
    If the user has a non-English locale set, returns a <script> tag pointing
    to the relevant locale JavaScript file
    """
    request = context["request"]

    try:
        lang_code = request.LANGUAGE_CODE
    except AttributeError:
        # it's possible that request at this point, LANGUAGE_CODE hasn't be bound
        # to the Request object yet. This specifically happens when rendering our own
        # 500 error page, resulting in yet another error trying to render our error.
        return ""

    if lang_code == "en" or lang_code not in settings.SUPPORTED_LANGUAGES:
        return ""

    nonce = ""
    if hasattr(request, "csp_nonce"):
        nonce = ' nonce="{}"'.format(request.csp_nonce)

    href = get_asset_url("sentry", "dist/locale/" + lang_code + ".js")
    return mark_safe('<script src="{}"{}{}></script>'.format(
        href, crossorigin(), nonce))
Exemple #3
0
 def serialize(self, obj, attrs, user):
     contexts = []
     if hasattr(obj, 'get_custom_contexts'):
         contexts.extend(x.type for x in obj.get_custom_contexts() or ())
     d = {
         'id':
         obj.slug,
         'name':
         six.text_type(obj.get_title()),
         'type':
         obj.get_plugin_type(),
         'canDisable':
         obj.can_disable,
         'isTestable':
         obj.is_testable(),
         'metadata':
         obj.get_metadata(),
         'contexts':
         contexts,
         'status':
         obj.get_status(),
         'assets': [{
             'url':
             absolute_uri(get_asset_url(obj.asset_key or obj.slug, asset)),
         } for asset in obj.get_assets()],
     }
     if self.project:
         d['enabled'] = obj.is_enabled(self.project)
     return d
Exemple #4
0
def get_react_config(context):
    if 'request' in context:
        user = context['request'].user
    else:
        user = None

    if user:
        user = extract_lazy_object(user)

    enabled_features = []
    if features.has('organizations:create', actor=user):
        enabled_features.append('organizations:create')
    if features.has('auth:register', actor=user):
        enabled_features.append('auth:register')

    context = {
        'singleOrganization': settings.SENTRY_SINGLE_ORGANIZATION,
        'urlPrefix': settings.SENTRY_URL_PREFIX,
        'version': _get_version_info(),
        'features': enabled_features,
        'mediaUrl': get_asset_url('sentry', ''),
    }
    if user and user.is_authenticated():
        context.update({
            'isAuthenticated': True,
            'user': serialize(user, user),
        })
    else:
        context.update({
            'isAuthenticated': False,
            'user': None,
        })
    return mark_safe(json.dumps(context))
Exemple #5
0
 def test_simple(self):
     logo_url = absolute_uri(get_asset_url("sentry", "images/sentry-email-avatar.png"))
     alert_rule = self.create_alert_rule()
     incident = self.create_incident(alert_rule=alert_rule, status=2)
     trigger = self.create_alert_rule_trigger(alert_rule, CRITICAL_TRIGGER_LABEL, 100)
     self.create_alert_rule_trigger_action(
         alert_rule_trigger=trigger, triggered_for_incident=incident
     )
     title = f"Resolved: {alert_rule.name}"
     incident_footer_ts = (
         "<!date^{:.0f}^Sentry Incident - Started {} at {} | Sentry Incident>".format(
             to_timestamp(incident.date_started), "{date_pretty}", "{time}"
         )
     )
     assert SlackIncidentsMessageBuilder(incident, IncidentStatus.CLOSED).build() == {
         "fallback": title,
         "title": title,
         "title_link": absolute_uri(
             reverse(
                 "sentry-metric-alert",
                 kwargs={
                     "organization_slug": incident.organization.slug,
                     "incident_id": incident.identifier,
                 },
             )
         ),
         "text": "0 events in the last 10 minutes\nFilter: level:error",
         "fields": [],
         "mrkdwn_in": ["text"],
         "footer_icon": logo_url,
         "footer": incident_footer_ts,
         "color": LEVEL_TO_COLOR["_incident_resolved"],
         "actions": [],
     }
Exemple #6
0
    def test_simple(self):
        logo_url = absolute_uri(get_asset_url("sentry", "images/sentry-email-avatar.png"))

        incident = self.create_incident()
        title = "INCIDENT: {} (#{})".format(incident.title, incident.identifier)
        assert build_incident_attachment(incident) == {
            "fallback": title,
            "title": title,
            "title_link": absolute_uri(
                reverse(
                    "sentry-incident",
                    kwargs={
                        "organization_slug": incident.organization.slug,
                        "incident_id": incident.identifier,
                    },
                )
            ),
            "text": " ",
            "fields": [
                {"title": "Status", "value": "Open", "short": True},
                {"title": "Events", "value": 0, "short": True},
                {"title": "Users", "value": 0, "short": True},
            ],
            "mrkdwn_in": ["text"],
            "footer_icon": logo_url,
            "footer": "Sentry Incident",
            "ts": to_timestamp(incident.date_started),
            "color": LEVEL_TO_COLOR["error"],
            "actions": [],
        }
Exemple #7
0
    def test_simple(self):
        logo_url = absolute_uri(get_asset_url('sentry', 'images/sentry-email-avatar.png'))

        incident = self.create_incident()
        title = 'INCIDENT: {} (#{})'.format(incident.title, incident.identifier)
        assert build_incident_attachment(incident) == {
            'fallback': title,
            'title': title,
            'title_link': absolute_uri(reverse(
                'sentry-incident',
                kwargs={
                    'organization_slug': incident.organization.slug,
                    'incident_id': incident.identifier,
                },
            )),
            'text': ' ',
            'fields': [
                {'title': 'Status', 'value': 'Open', 'short': True},
                {'title': 'Events', 'value': 0, 'short': True},
                {'title': 'Users', 'value': 0, 'short': True},
            ],
            'mrkdwn_in': ['text'],
            'footer_icon': logo_url,
            'footer': 'Sentry Incident',
            'ts': to_timestamp(incident.date_started),
            'color': LEVEL_TO_COLOR['error'],
            'actions': [],
        }
Exemple #8
0
    def serialize(self, obj, attrs, user):
        doc = ''

        contexts = []
        if hasattr(obj, 'get_custom_contexts'):
            contexts.extend(x.type for x in obj.get_custom_contexts() or ())
        d = {
            'id':
            obj.slug,
            'name':
            six.text_type(obj.get_title()),
            'slug':
            obj.slug or slugify(six.text_type(obj.get_title())),
            'shortName':
            six.text_type(obj.get_short_title()),
            'type':
            obj.get_plugin_type(),
            'canDisable':
            obj.can_disable,
            'isTestable':
            hasattr(obj, 'is_testable') and obj.is_testable(),
            'hasConfiguration':
            obj.has_project_conf(),
            'metadata':
            obj.get_metadata(),
            'contexts':
            contexts,
            'status':
            obj.get_status(),
            'assets': [{
                'url':
                absolute_uri(get_asset_url(obj.asset_key or obj.slug, asset)),
            } for asset in obj.get_assets()],
            'doc':
            doc,
        }
        if self.project:
            d['enabled'] = obj.is_enabled(self.project)

        if obj.version:
            d['version'] = six.text_type(obj.version)

        if obj.author:
            d['author'] = {
                'name': six.text_type(obj.author),
                'url': six.text_type(obj.author_url)
            }

        d['isHidden'] = d.get('enabled', False) is False and obj.is_hidden()

        if obj.description:
            d['description'] = six.text_type(obj.description)

        if obj.resource_links:
            d['resourceLinks'] = [{
                'title': title,
                'url': url
            } for [title, url] in obj.resource_links]

        return d
Exemple #9
0
    def serialize(self, obj, attrs, user):
        from sentry.api.endpoints.project_releases_token import _get_webhook_url
        doc = ''

        if self.project is not None:
            release_token = ProjectOption.objects.get_value(self.project, 'sentry:release-token')
            if release_token is not None:
                webhook_url = _get_webhook_url(self.project, obj.slug, release_token)

                if hasattr(obj, 'get_release_doc_html'):
                    try:
                        doc = obj.get_release_doc_html(webhook_url)
                    except NotImplementedError:
                        pass

        contexts = []
        if hasattr(obj, 'get_custom_contexts'):
            contexts.extend(x.type for x in obj.get_custom_contexts() or ())
        d = {
            'id': obj.slug,
            'name': six.text_type(obj.get_title()),
            'slug': obj.slug or slugify(six.text_type(obj.get_title())),
            'shortName': six.text_type(obj.get_short_title()),
            'type': obj.get_plugin_type(),
            'canDisable': obj.can_disable,
            'isTestable': hasattr(obj, 'is_testable') and obj.is_testable(),
            'hasConfiguration': obj.has_project_conf(),
            'metadata': obj.get_metadata(),
            'contexts': contexts,
            'status': obj.get_status(),
            'assets': [
                {
                    'url': absolute_uri(get_asset_url(obj.asset_key or obj.slug, asset)),
                } for asset in obj.get_assets()
            ],
            'doc': doc,
        }
        if self.project:
            d['enabled'] = obj.is_enabled(self.project)

        if obj.version:
            d['version'] = six.text_type(obj.version)

        if obj.author:
            d['author'] = {
                'name': six.text_type(obj.author),
                'url': six.text_type(obj.author_url)
            }

        d['isHidden'] = d.get('enabled', False) is False and obj.is_hidden()

        if obj.description:
            d['description'] = six.text_type(obj.description)

        if obj.resource_links:
            d['resourceLinks'] = [
                {'title': title, 'url': url} for [title, url] in obj.resource_links
            ]

        return d
Exemple #10
0
def build_linked_card():
    image = {
        "type":
        "Image",
        "url":
        absolute_uri(get_asset_url("sentry", "images/sentry-glyph-black.png")),
        "size":
        "Large",
    }
    desc = {
        "type": "TextBlock",
        "text":
        "Your Microsoft Teams identity has been linked to your Sentry account. You're good to go.",
        "size": "Large",
        "wrap": True,
    }
    body = {
        "type":
        "ColumnSet",
        "columns": [
            {
                "type": "Column",
                "items": [image],
                "width": "auto"
            },
            {
                "type": "Column",
                "items": [desc]
            },
        ],
    }
    return {
        "type": "AdaptiveCard",
        "body": [body],
    }
Exemple #11
0
 def test_simple(self):
     logo_url = absolute_uri(
         get_asset_url("sentry", "images/sentry-email-avatar.png"))
     alert_rule = self.create_alert_rule()
     incident = self.create_incident(alert_rule=alert_rule, status=2)
     title = u"{}: {}".format("Resolved", alert_rule.name)
     assert build_incident_attachment(incident) == {
         "fallback":
         title,
         "title":
         title,
         "title_link":
         absolute_uri(
             reverse(
                 "sentry-metric-alert",
                 kwargs={
                     "organization_slug": incident.organization.slug,
                     "incident_id": incident.identifier,
                 },
             )),
         "text":
         "0 events in the last 10 minutes\nFilter: level:error",
         "fields": [],
         "mrkdwn_in": ["text"],
         "footer_icon":
         logo_url,
         "footer":
         "Sentry Incident",
         "ts":
         to_timestamp(incident.date_started),
         "color":
         RESOLVED_COLOR,
         "actions": [],
     }
Exemple #12
0
def build_incident_attachment(incident):
    logo_url = absolute_uri(get_asset_url('sentry', 'images/sentry-email-avatar.png'))

    aggregates = get_incident_aggregates(incident)
    status = 'Closed' if incident.status == IncidentStatus.CLOSED.value else 'Open'

    fields = [
        {'title': 'Status', 'value': status, 'short': True},
        {'title': 'Events', 'value': aggregates['count'], 'short': True},
        {'title': 'Users', 'value': aggregates['unique_users'], 'short': True},
    ]

    ts = incident.date_started

    return {
        'fallback': u'{} (#{})'.format(incident.title, incident.identifier),
        'title': u'{} (#{})'.format(incident.title, incident.identifier),
        'title_link': absolute_uri(reverse(
            'sentry-incident',
            kwargs={
                'organization_slug': incident.organization.slug,
                'incident_id': incident.identifier,
            },
        )),
        'text': ' ',
        'fields': fields,
        'mrkdwn_in': ['text'],
        'footer_icon': logo_url,
        'footer': 'Sentry Incident',
        'ts': to_timestamp(ts),
        'color': LEVEL_TO_COLOR['error'],
        'actions': [],
    }
Exemple #13
0
def get_react_config(context):
    if 'request' in context:
        user = getattr(context['request'], 'user', None) or AnonymousUser()
        messages = get_messages(context['request'])
        try:
            is_superuser = context['request'].is_superuser()
        except AttributeError:
            is_superuser = False
    else:
        user = None
        messages = []
        is_superuser = False

    if user:
        user = extract_lazy_object(user)

    enabled_features = []
    if features.has('organizations:create', actor=user):
        enabled_features.append('organizations:create')
    if auth.has_user_registration():
        enabled_features.append('auth:register')

    version_info = _get_version_info()

    needs_upgrade = False

    if is_superuser:
        needs_upgrade = _needs_upgrade()

    context = {
        'singleOrganization': settings.SENTRY_SINGLE_ORGANIZATION,
        'supportEmail': get_support_mail(),
        'urlPrefix': options.get('system.url-prefix'),
        'version': version_info,
        'features': enabled_features,
        'mediaUrl': get_asset_url('sentry', ''),
        'needsUpgrade': needs_upgrade,
        'dsn': _get_public_dsn(),
        'statuspage': _get_statuspage(),
        'messages': [{
            'message': msg.message,
            'level': msg.tags,
        } for msg in messages],
        'isOnPremise': settings.SENTRY_ONPREMISE,
        'invitesEnabled': settings.SENTRY_ENABLE_INVITES,
        'gravatarBaseUrl': settings.SENTRY_GRAVATAR_BASE_URL,
    }
    if user and user.is_authenticated():
        context.update({
            'isAuthenticated': True,
            'user': serialize(user, user),
        })
        context['user']['isSuperuser'] = is_superuser
    else:
        context.update({
            'isAuthenticated': False,
            'user': None,
        })
    return json.dumps_htmlsafe(context)
Exemple #14
0
def build_installation_confirmation_message(organization):
    logo = {
        "type":
        "Image",
        "url":
        absolute_uri(get_asset_url("sentry", "images/sentry-glyph-black.png")),
        "size":
        "Medium",
    }
    welcome = {
        "type": "TextBlock",
        "weight": "Bolder",
        "size": "Large",
        "text": u"Installation for {} is successful".format(organization.name),
        "wrap": True,
    }
    alert_rule_instructions = {
        "type": "TextBlock",
        "text":
        "Now that setup is complete, you can continue by configuring alerts.",
        "wrap": True,
    }
    alert_rule_url = absolute_uri("organizations/{}/rules/".format(
        organization.slug))
    alert_rule_button = {
        "type": "Action.OpenUrl",
        "title": "Add Alert Rules",
        "url": alert_rule_url,
    }
    return {
        "type":
        "AdaptiveCard",
        "body": [
            {
                "type":
                "ColumnSet",
                "columns": [
                    {
                        "type": "Column",
                        "items": [logo],
                        "width": "auto"
                    },
                    {
                        "type": "Column",
                        "items": [welcome],
                        "width": "stretch",
                        "verticalContentAlignment": "Center",
                    },
                ],
            },
            alert_rule_instructions,
        ],
        "actions": [alert_rule_button],
        "$schema":
        "http://adaptivecards.io/schemas/adaptive-card.json",
        "version":
        "1.2",
    }
Exemple #15
0
def build_incident_attachment(incident):
    logo_url = absolute_uri(
        get_asset_url("sentry", "images/sentry-email-avatar.png"))

    aggregates = get_incident_aggregates(incident)
    status = "Closed" if incident.status == IncidentStatus.CLOSED.value else "Open"

    fields = [
        {
            "title": "Status",
            "value": status,
            "short": True
        },
        {
            "title": "Events",
            "value": aggregates["count"],
            "short": True
        },
        {
            "title": "Users",
            "value": aggregates["unique_users"],
            "short": True
        },
    ]

    ts = incident.date_started

    title = u"INCIDENT: {} (#{})".format(incident.title, incident.identifier)

    return {
        "fallback":
        title,
        "title":
        title,
        "title_link":
        absolute_uri(
            reverse(
                "sentry-incident",
                kwargs={
                    "organization_slug": incident.organization.slug,
                    "incident_id": incident.identifier,
                },
            )),
        "text":
        " ",
        "fields":
        fields,
        "mrkdwn_in": ["text"],
        "footer_icon":
        logo_url,
        "footer":
        "Sentry Incident",
        "ts":
        to_timestamp(ts),
        "color":
        LEVEL_TO_COLOR["error"],
        "actions": [],
    }
Exemple #16
0
def incident_attachment_info(incident, metric_value=None):
    logo_url = absolute_uri(
        get_asset_url("sentry", "images/sentry-email-avatar.png"))
    alert_rule = incident.alert_rule

    incident_trigger = (IncidentTrigger.objects.filter(
        incident=incident).order_by("-date_modified").first())
    if incident_trigger:
        alert_rule_trigger = incident_trigger.alert_rule_trigger
        # TODO: If we're relying on this and expecting possible delays between a trigger fired and this function running,
        # then this could actually be incorrect if they changed the trigger's time window in this time period. Should we store it?
        start = incident_trigger.date_modified - timedelta(
            seconds=alert_rule_trigger.alert_rule.snuba_query.time_window)
        end = incident_trigger.date_modified
    else:
        start, end = None, None

    if incident.status == IncidentStatus.CLOSED.value:
        status = "Resolved"
    elif incident.status == IncidentStatus.WARNING.value:
        status = "Warning"
    elif incident.status == IncidentStatus.CRITICAL.value:
        status = "Critical"

    agg_text = QUERY_AGGREGATION_DISPLAY.get(alert_rule.snuba_query.aggregate,
                                             alert_rule.snuba_query.aggregate)
    if metric_value is None:
        metric_value = get_incident_aggregates(
            incident, start, end, use_alert_aggregate=True)["count"]
    time_window = alert_rule.snuba_query.time_window // 60

    text = "{} {} in the last {} minutes".format(metric_value, agg_text,
                                                 time_window)

    if alert_rule.snuba_query.query != "":
        text = text + "\nFilter: {}".format(alert_rule.snuba_query.query)

    ts = incident.date_started

    title = u"{}: {}".format(status, alert_rule.name)

    title_link = absolute_uri(
        reverse(
            "sentry-metric-alert",
            kwargs={
                "organization_slug": incident.organization.slug,
                "incident_id": incident.identifier,
            },
        ))

    return {
        "title": title,
        "text": text,
        "logo_url": logo_url,
        "status": status,
        "ts": ts,
        "title_link": title_link,
    }
Exemple #17
0
    def serialize(self, obj, attrs, user):
        from sentry.api.endpoints.project_releases_token import _get_webhook_url

        doc = ""

        if self.project is not None:
            release_token = ProjectOption.objects.get_value(self.project, "sentry:release-token")
            if release_token is not None:
                webhook_url = _get_webhook_url(self.project, obj.slug, release_token)

                if hasattr(obj, "get_release_doc_html"):
                    try:
                        doc = obj.get_release_doc_html(webhook_url)
                    except NotImplementedError:
                        pass

        contexts = []
        if hasattr(obj, "get_custom_contexts"):
            contexts.extend(x.type for x in obj.get_custom_contexts() or ())
        d = {
            "id": obj.slug,
            "name": six.text_type(obj.get_title()),
            "slug": obj.slug or slugify(six.text_type(obj.get_title())),
            "shortName": six.text_type(obj.get_short_title()),
            "type": obj.get_plugin_type(),
            "canDisable": obj.can_disable,
            "isTestable": hasattr(obj, "is_testable") and obj.is_testable(),
            "hasConfiguration": obj.has_project_conf(),
            "metadata": obj.get_metadata(),
            "contexts": contexts,
            "status": obj.get_status(),
            "assets": [
                {"url": absolute_uri(get_asset_url(obj.asset_key or obj.slug, asset))}
                for asset in obj.get_assets()
            ],
            "doc": doc,
        }
        if self.project:
            d["enabled"] = obj.is_enabled(self.project)

        if obj.version:
            d["version"] = six.text_type(obj.version)

        if obj.author:
            d["author"] = {"name": six.text_type(obj.author), "url": six.text_type(obj.author_url)}

        d["isHidden"] = d.get("enabled", False) is False and obj.is_hidden()

        if obj.description:
            d["description"] = six.text_type(obj.description)

        if obj.resource_links:
            d["resourceLinks"] = [
                {"title": title, "url": url} for [title, url] in obj.resource_links
            ]

        return d
Exemple #18
0
def absolute_asset_url(module, path):
    """
    Returns a versioned absolute asset URL (located within Sentry's static files).

    Example:
      {% absolute_asset_url 'sentry' 'dist/sentry.css' %}
      =>  "http://sentry.example.com/_static/74d127b78dc7daf2c51f/sentry/dist/sentry.css"
    """
    return absolute_uri(get_asset_url(module, path))
Exemple #19
0
def absolute_asset_url(module, path):
    """
    Returns a versioned absolute asset URL (located within Sentry's static files).

    Example:
      {% absolute_asset_url 'sentry' 'dist/sentry.css' %}
      =>  "http://sentry.example.com/_static/74d127b78dc7daf2c51f/sentry/dist/sentry.css"
    """
    return absolute_uri(get_asset_url(module, path))
Exemple #20
0
def asset_url(module, path):
    """
    Returns a versioned asset URL (located within Sentry's static files).

    Example:
      {% asset_url 'sentry' 'dist/sentry.css' %}
      =>  "/_static/74d127b78dc7daf2c51f/sentry/dist/sentry.css"
    """
    return get_asset_url(module, path)
Exemple #21
0
def incident_attachment_info(incident, metric_value=None, action=None, method=None):
    logo_url = absolute_uri(get_asset_url("sentry", "images/sentry-email-avatar.png"))
    alert_rule = incident.alert_rule

    status = INCIDENT_STATUS[incident_status_info(incident, metric_value, action, method)]

    agg_text = QUERY_AGGREGATION_DISPLAY.get(
        alert_rule.snuba_query.aggregate, alert_rule.snuba_query.aggregate
    )
    if metric_value is None:
        incident_trigger = (
            IncidentTrigger.objects.filter(incident=incident).order_by("-date_modified").first()
        )
        if incident_trigger:
            alert_rule_trigger = incident_trigger.alert_rule_trigger
            # TODO: If we're relying on this and expecting possible delays between a
            # trigger fired and this function running, then this could actually be
            # incorrect if they changed the trigger's time window in this time period.
            # Should we store it?
            start = incident_trigger.date_modified - timedelta(
                seconds=alert_rule_trigger.alert_rule.snuba_query.time_window
            )
            end = incident_trigger.date_modified
        else:
            start, end = None, None
        metric_value = get_incident_aggregates(incident, start, end, use_alert_aggregate=True)[
            "count"
        ]
    time_window = alert_rule.snuba_query.time_window // 60

    text = f"{metric_value} {agg_text} in the last {time_window} minutes"
    if alert_rule.snuba_query.query != "":
        text += f"\nFilter: {alert_rule.snuba_query.query}"

    ts = incident.date_started

    title = f"{status}: {alert_rule.name}"

    title_link = absolute_uri(
        reverse(
            "sentry-metric-alert",
            kwargs={
                "organization_slug": incident.organization.slug,
                "incident_id": incident.identifier,
            },
        )
    )

    return {
        "title": title,
        "text": text,
        "logo_url": logo_url,
        "status": status,
        "ts": ts,
        "title_link": title_link,
    }
Exemple #22
0
def get_react_config(context):
    if 'request' in context:
        user = context['request'].user
        messages = get_messages(context['request'])
        try:
            is_superuser = context['request'].is_superuser()
        except AttributeError:
            is_superuser = False
    else:
        user = None
        messages = []
        is_superuser = False

    if user:
        user = extract_lazy_object(user)

    enabled_features = []
    if features.has('organizations:create', actor=user):
        enabled_features.append('organizations:create')
    if features.has('auth:register', actor=user):
        enabled_features.append('auth:register')

    version_info = _get_version_info()

    needs_upgrade = False

    if is_superuser:
        needs_upgrade = _needs_upgrade()

    context = {
        'singleOrganization': settings.SENTRY_SINGLE_ORGANIZATION,
        'urlPrefix': options.get('system.url-prefix'),
        'version': version_info,
        'features': enabled_features,
        'mediaUrl': get_asset_url('sentry', ''),
        'needsUpgrade': needs_upgrade,
        'dsn': _get_public_dsn(),
        'statuspage': _get_statuspage(),
        'messages': [{
            'message': msg.message,
            'level': msg.tags,
        } for msg in messages],
    }
    if user and user.is_authenticated():
        context.update({
            'isAuthenticated': True,
            'user': serialize(user, user),
        })
        context['user']['isSuperuser'] = is_superuser
    else:
        context.update({
            'isAuthenticated': False,
            'user': None,
        })
    return mark_safe(json.dumps(context))
Exemple #23
0
def build_incident_attachment(incident):
    logo_url = absolute_uri(get_asset_url("sentry", "images/sentry-email-avatar.png"))
    alert_rule = incident.alert_rule
    aggregates = get_incident_aggregates(incident)

    if incident.status == IncidentStatus.CLOSED.value:
        status = "Resolved"
        color = RESOLVED_COLOR
    elif incident.status == IncidentStatus.WARNING.value:
        status = "Warning"
        color = LEVEL_TO_COLOR["warning"]
    elif incident.status == IncidentStatus.CRITICAL.value:
        status = "Critical"
        color = LEVEL_TO_COLOR["fatal"]

    agg_text = QUERY_AGGREGATION_DISPLAY[alert_rule.aggregation]

    agg_value = (
        aggregates["count"]
        if alert_rule.aggregation == QueryAggregations.TOTAL.value
        else aggregates["unique_users"]
    )
    time_window = alert_rule.time_window

    text = "{} {} in the last {} minutes".format(agg_value, agg_text, time_window)

    if alert_rule.query != "":
        text = text + "\Filter: {}".format(alert_rule.query)

    ts = incident.date_started

    title = u"{}: {}".format(status, alert_rule.name)

    return {
        "fallback": title,
        "title": title,
        "title_link": absolute_uri(
            reverse(
                "sentry-metric-alert",
                kwargs={
                    "organization_slug": incident.organization.slug,
                    "incident_id": incident.identifier,
                },
            )
        ),
        "text": text,
        "fields": [],
        "mrkdwn_in": ["text"],
        "footer_icon": logo_url,
        "footer": "Sentry Incident",
        "ts": to_timestamp(ts),
        "color": color,
        "actions": [],
    }
Exemple #24
0
def build_group_footer(group, rules, project, event):
    # TODO: implement with event as well
    image_column = {
        "type": "Column",
        "items": [
            {
                "type": "Image",
                "url": absolute_uri(get_asset_url("sentry", "images/sentry-glyph-black.png")),
                "height": "20px",
            }
        ],
        "width": "auto",
    }

    text = f"{group.qualified_short_id}"
    if rules:
        rule_url = build_rule_url(rules[0], group, project)
        text += f" via [{rules[0].label}]({rule_url})"
        if len(rules) > 1:
            text += f" (+{len(rules) - 1} other)"

    text_column = {
        "type": "Column",
        "items": [{"type": "TextBlock", "size": "Small", "weight": "Lighter", "text": text}],
        "isSubtle": True,
        "width": "auto",
        "spacing": "none",
    }

    date_ts = group.last_seen
    if event:
        event_ts = event.datetime
        date_ts = max(date_ts, event_ts)

    date = date_ts.replace(microsecond=0).isoformat()
    date_text = f"{{{{DATE({date}, SHORT)}}}} at {{{{TIME({date})}}}}"
    date_column = {
        "type": "Column",
        "items": [
            {
                "type": "TextBlock",
                "size": "Small",
                "weight": "Lighter",
                "horizontalAlignment": "Center",
                "text": date_text,
            }
        ],
        "width": "auto",
    }

    return {"type": "ColumnSet", "columns": [image_column, text_column, date_column]}
Exemple #25
0
def get_react_config(context):
    if 'request' in context:
        user = context['request'].user
        messages = get_messages(context['request'])
        try:
            is_superuser = context['request'].is_superuser()
        except AttributeError:
            is_superuser = False
    else:
        user = None
        messages = []
        is_superuser = False

    if user:
        user = extract_lazy_object(user)

    enabled_features = []
    if features.has('organizations:create', actor=user):
        enabled_features.append('organizations:create')
    if features.has('auth:register', actor=user):
        enabled_features.append('auth:register')

    context = {
        'singleOrganization':
        settings.SENTRY_SINGLE_ORGANIZATION,
        'urlPrefix':
        settings.SENTRY_URL_PREFIX,
        'version':
        _get_version_info(),
        'features':
        enabled_features,
        'mediaUrl':
        get_asset_url('sentry', ''),
        'messages': [{
            'message': msg.message,
            'level': msg.tags,
        } for msg in messages],
    }
    if user and user.is_authenticated():
        context.update({
            'isAuthenticated': True,
            'user': serialize(user, user),
        })
        context['user']['isSuperuser'] = is_superuser
    else:
        context.update({
            'isAuthenticated': False,
            'user': None,
        })
    return mark_safe(json.dumps(context))
Exemple #26
0
 def get(self, request):
     sentry_logo = absolute_uri(get_asset_url("sentry", "images/logos/logo-sentry.svg"))
     return self.respond(
         {
             "name": "Sentry",
             "description": "Sentry",
             "key": JIRA_KEY,
             "baseUrl": absolute_uri(),
             "vendor": {"name": "Sentry", "url": "https://sentry.io"},
             "authentication": {"type": "jwt"},
             "lifecycle": {
                 "installed": "/extensions/jira/installed/",
                 "uninstalled": "/extensions/jira/uninstalled/",
             },
             "apiVersion": 1,
             "modules": {
                 "postInstallPage": {
                     "url": "/extensions/jira/ui-hook",
                     "name": {"value": "Configure Sentry Add-on"},
                     "key": "post-install-sentry",
                 },
                 "configurePage": {
                     "url": "/extensions/jira/ui-hook",
                     "name": {"value": "Configure Sentry Add-on"},
                     "key": "configure-sentry",
                 },
                 "jiraIssueGlances": [
                     {
                         "icon": {"width": 24, "height": 24, "url": sentry_logo},
                         "content": {"type": "label", "label": {"value": "Linked Issues"}},
                         "target": {
                             "type": "web_panel",
                             "url": "/extensions/jira/issue/{issue.key}/",
                         },
                         "name": {"value": "Sentry "},
                         "key": "sentry-issues-glance",
                     }
                 ],
                 "webhooks": [
                     {
                         "event": "jira:issue_updated",
                         "url": reverse("sentry-extensions-jira-issue-updated"),
                         "excludeBody": False,
                     }
                 ],
             },
             "apiMigrations": {"gdpr": True},
             "scopes": scopes,
         }
     )
Exemple #27
0
    def serialize(self, obj, attrs, user):
        from sentry.api.endpoints.project_releases_token import _get_webhook_url
        doc = ''

        release_token = ProjectOption.objects.get_value(
            self.project, 'sentry:release-token')
        if release_token is not None:
            webhook_url = _get_webhook_url(self.project, obj.slug,
                                           release_token)

            if hasattr(obj, 'get_release_doc_html'):
                try:
                    doc = obj.get_release_doc_html(webhook_url)
                except NotImplementedError:
                    pass

        contexts = []
        if hasattr(obj, 'get_custom_contexts'):
            contexts.extend(x.type for x in obj.get_custom_contexts() or ())
        d = {
            'id':
            obj.slug,
            'name':
            six.text_type(obj.get_title()),
            'type':
            obj.get_plugin_type(),
            'canDisable':
            obj.can_disable,
            'isTestable':
            hasattr(obj, 'is_testable') and obj.is_testable(),
            'metadata':
            obj.get_metadata(),
            'contexts':
            contexts,
            'status':
            obj.get_status(),
            'assets': [{
                'url':
                absolute_uri(get_asset_url(obj.asset_key or obj.slug, asset)),
            } for asset in obj.get_assets()],
            'doc':
            doc,
        }
        if self.project:
            d['enabled'] = obj.is_enabled(self.project)
        return d
Exemple #28
0
 def serialize(self, obj, attrs, user):
     d = {
         'id': obj.slug,
         'name': six.text_type(obj.get_title()),
         'type': obj.get_plugin_type(),
         'canDisable': obj.can_disable,
         'isTestable': obj.is_testable(),
         'metadata': obj.get_metadata(),
         'assets': [
             {
                 'url': absolute_uri(get_asset_url(obj.asset_key or obj.slug, asset)),
             }
             for asset in obj.get_assets()
         ],
     }
     if self.project:
         d['enabled'] = obj.is_enabled(self.project)
     return d
Exemple #29
0
 def test_metric_value(self):
     logo_url = absolute_uri(
         get_asset_url("sentry", "images/sentry-email-avatar.png"))
     alert_rule = self.create_alert_rule()
     incident = self.create_incident(alert_rule=alert_rule, status=2)
     title = f"Critical: {alert_rule.name}"  # This test will use the action/method and not the incident to build status
     metric_value = 5000
     trigger = self.create_alert_rule_trigger(alert_rule,
                                              CRITICAL_TRIGGER_LABEL, 100)
     action = self.create_alert_rule_trigger_action(
         alert_rule_trigger=trigger, triggered_for_incident=incident)
     incident_footer_ts = (
         "<!date^{:.0f}^Sentry Incident - Started {} at {} | Sentry Incident>"
         .format(to_timestamp(incident.date_started), "{date_pretty}",
                 "{time}"))
     # This should fail because it pulls status from `action` instead of `incident`
     assert build_incident_attachment(
         action, incident, metric_value=metric_value, method="fire"
     ) == {
         "fallback":
         title,
         "title":
         title,
         "title_link":
         absolute_uri(
             reverse(
                 "sentry-metric-alert",
                 kwargs={
                     "organization_slug": incident.organization.slug,
                     "incident_id": incident.identifier,
                 },
             )),
         "text":
         f"{metric_value} events in the last 10 minutes\nFilter: level:error",
         "fields": [],
         "mrkdwn_in": ["text"],
         "footer_icon":
         logo_url,
         "footer":
         incident_footer_ts,
         "color":
         LEVEL_TO_COLOR["fatal"],
         "actions": [],
     }
Exemple #30
0
    def _build(
        text: str,
        title: str | None = None,
        title_link: str | None = None,
        footer: str | None = None,
        color: str | None = None,
        actions: Sequence[MessageAction] | None = None,
        **kwargs: Any,
    ) -> SlackBody:
        """
        Helper to DRY up Slack specific fields.

        :param string text: Body text.
        :param [string] title: Title text.
        :param [string] title_link: Optional URL attached to the title.
        :param [string] footer: Footer text.
        :param [string] color: The key in the Slack palate table, NOT hex. Default: "info".
        :param [list[MessageAction]] actions: List of actions displayed alongside the message.
        :param kwargs: Everything else.
        """
        # If `footer` string is passed, automatically attach a `footer_icon`.
        if footer:
            kwargs["footer"] = footer
            kwargs["footer_icon"] = str(
                absolute_uri(
                    get_asset_url("sentry", "images/sentry-email-avatar.png")))

        if title:
            kwargs["title"] = title
            if title_link:
                kwargs["title_link"] = title_link

        if actions is not None:
            kwargs["actions"] = [
                get_slack_button(action) for action in actions
            ]

        return {
            "text": text,
            "mrkdwn_in": ["text"],
            "color": LEVEL_TO_COLOR[color or "info"],
            **kwargs,
        }
Exemple #31
0
    def get(self, request: Request, *args, **kwargs) -> Response:
        try:
            integration = get_integration_from_request(request, "jira")
        except AtlassianConnectValidationError:
            return self.get_response({"error_message": "Unable to verify installation."})
        except ExpiredSignatureError:
            return self.get_response({"refresh_required": True})

        # expose a link to the configuration view
        signed_data = {
            "external_id": integration.external_id,
            "metadata": json.dumps(integration.metadata),
        }
        finish_link = "{}.?signed_params={}".format(
            absolute_uri("/extensions/jira/configure/"), sign(**signed_data)
        )

        image_path = absolute_uri(get_asset_url("sentry", "images/sentry-glyph-black.png"))
        return self.get_response({"finish_link": finish_link, "image_path": image_path})
Exemple #32
0
def locale_js_include(context):
    """
    If the user has a non-English locale set, returns a <script> tag pointing
    to the relevant locale JavaScript file
    """
    request = context['request']

    try:
        lang_code = request.LANGUAGE_CODE
    except AttributeError:
        # it's possible that request at this point, LANGUAGE_CODE hasn't be bound
        # to the Request object yet. This specifically happens when rendering our own
        # 500 error page, resulting in yet another error trying to render our error.
        return ''

    if lang_code == 'en' or lang_code not in settings.SUPPORTED_LANGUAGES:
        return ''

    href = get_asset_url("sentry", "dist/moment/locale/" + lang_code + ".js")
    return "<script src=\"{0}\"{1}></script>".format(href, crossorigin())
Exemple #33
0
    def serialize(self, obj, attrs, user):
        from sentry.api.endpoints.project_releases_token import _get_webhook_url
        doc = ''

        release_token = ProjectOption.objects.get_value(self.project, 'sentry:release-token')
        if release_token is not None:
            webhook_url = _get_webhook_url(self.project, obj.slug, release_token)

            if hasattr(obj, 'get_release_doc_html'):
                try:
                    doc = obj.get_release_doc_html(webhook_url)
                except NotImplementedError:
                    pass

        contexts = []
        if hasattr(obj, 'get_custom_contexts'):
            contexts.extend(x.type for x in obj.get_custom_contexts() or ())
        d = {
            'id': obj.slug,
            'name': six.text_type(obj.get_title()),
            'shortName': six.text_type(obj.get_short_title()),
            'type': obj.get_plugin_type(),
            'canDisable': obj.can_disable,
            'isTestable': hasattr(obj, 'is_testable') and obj.is_testable(),
            'metadata': obj.get_metadata(),
            'contexts': contexts,
            'status': obj.get_status(),
            'assets': [
                {
                    'url': absolute_uri(get_asset_url(obj.asset_key or obj.slug, asset)),
                } for asset in obj.get_assets()
            ],
            'doc': doc,
        }
        if self.project:
            d['enabled'] = obj.is_enabled(self.project)
        return d
Exemple #34
0
def build_incident_attachment(incident):
    logo_url = absolute_uri(
        get_asset_url("sentry", "images/sentry-email-avatar.png"))
    alert_rule = incident.alert_rule

    incident_trigger = (IncidentTrigger.objects.filter(
        incident=incident).order_by("-date_modified").first())
    if incident_trigger:
        alert_rule_trigger = incident_trigger.alert_rule_trigger
        # TODO: If we're relying on this and expecting possible delays between a trigger fired and this function running,
        # then this could actually be incorrect if they changed the trigger's time window in this time period. Should we store it?
        start = incident_trigger.date_modified - timedelta(
            minutes=alert_rule_trigger.alert_rule.time_window)
        end = incident_trigger.date_modified
    else:
        start, end = None, None
    aggregates = get_incident_aggregates(incident, start, end)

    if incident.status == IncidentStatus.CLOSED.value:
        status = "Resolved"
        color = RESOLVED_COLOR
    elif incident.status == IncidentStatus.WARNING.value:
        status = "Warning"
        color = LEVEL_TO_COLOR["warning"]
    elif incident.status == IncidentStatus.CRITICAL.value:
        status = "Critical"
        color = LEVEL_TO_COLOR["fatal"]

    agg_text = QUERY_AGGREGATION_DISPLAY[alert_rule.aggregation]

    agg_value = (aggregates["count"]
                 if alert_rule.aggregation == QueryAggregations.TOTAL.value
                 else aggregates["unique_users"])
    time_window = alert_rule.time_window

    text = "{} {} in the last {} minutes".format(agg_value, agg_text,
                                                 time_window)

    if alert_rule.query != "":
        text = text + "\nFilter: {}".format(alert_rule.query)

    ts = incident.date_started

    title = u"{}: {}".format(status, alert_rule.name)

    return {
        "fallback":
        title,
        "title":
        title,
        "title_link":
        absolute_uri(
            reverse(
                "sentry-metric-alert",
                kwargs={
                    "organization_slug": incident.organization.slug,
                    "incident_id": incident.identifier,
                },
            )),
        "text":
        text,
        "fields": [],
        "mrkdwn_in": ["text"],
        "footer_icon":
        logo_url,
        "footer":
        "Sentry Incident",
        "ts":
        to_timestamp(ts),
        "color":
        color,
        "actions": [],
    }
Exemple #35
0
def get_react_config(context):
    if 'request' in context:
        user = getattr(context['request'], 'user', None) or AnonymousUser()
        messages = get_messages(context['request'])
        session = getattr(context['request'], 'session', None)
        try:
            is_superuser = context['request'].is_superuser()
        except AttributeError:
            is_superuser = False
    else:
        user = None
        messages = []
        is_superuser = False

    if user:
        user = extract_lazy_object(user)
        is_superuser = user.is_superuser

    enabled_features = []
    if features.has('organizations:create', actor=user):
        enabled_features.append('organizations:create')
    if auth.has_user_registration():
        enabled_features.append('auth:register')

    version_info = _get_version_info()

    needs_upgrade = False

    if is_superuser:
        needs_upgrade = _needs_upgrade()

    context = {
        'singleOrganization': settings.SENTRY_SINGLE_ORGANIZATION,
        'supportEmail': get_support_mail(),
        'urlPrefix': options.get('system.url-prefix'),
        'version': version_info,
        'features': enabled_features,
        'mediaUrl': get_asset_url('sentry', ''),
        'needsUpgrade': needs_upgrade,
        'dsn': get_public_dsn(),
        'statuspage': _get_statuspage(),
        'messages': [{
            'message': msg.message,
            'level': msg.tags,
        } for msg in messages],
        'isOnPremise': settings.SENTRY_ONPREMISE,
        'invitesEnabled': settings.SENTRY_ENABLE_INVITES,
        'gravatarBaseUrl': settings.SENTRY_GRAVATAR_BASE_URL,
        'termsUrl': settings.TERMS_URL,
        'privacyUrl': settings.PRIVACY_URL,
        # Note `lastOrganization` should not be expected to update throughout frontend app lifecycle
        # It should only be used on a fresh browser nav to a path where an
        # organization is not in context
        'lastOrganization': session['activeorg'] if session and 'activeorg' in session else None,
    }
    if user and user.is_authenticated():
        context.update({
            'isAuthenticated': True,
            'user': serialize(user, user, DetailedUserSerializer()),
        })
        context['user']['isSuperuser'] = is_superuser
    else:
        context.update({
            'isAuthenticated': False,
            'user': None,
        })
    return json.dumps_htmlsafe(context)
Exemple #36
0
 def _get_sentry_avatar_url(self):
     url = '/images/sentry-email-avatar.png'
     return absolute_uri(get_asset_url('sentry', url))
Exemple #37
0
def build_attachment(group, event=None, tags=None, identity=None, actions=None, rules=None):
    # XXX(dcramer): options are limited to 100 choices, even when nested
    status = group.get_status()

    members = get_member_assignees(group)
    teams = get_team_assignees(group)

    logo_url = absolute_uri(get_asset_url('sentry', 'images/sentry-email-avatar.png'))
    color = LEVEL_TO_COLOR.get(
        event.get_tag('level'),
        'error') if event else LEVEL_TO_COLOR['error']

    text = build_attachment_text(group, event) or ''

    if actions is None:
        actions = []

    assignee = get_assignee(group)

    resolve_button = {
        'name': 'resolve_dialog',
        'value': 'resolve_dialog',
        'type': 'button',
        'text': 'Resolve...',
    }

    ignore_button = {
        'name': 'status',
        'value': 'ignored',
        'type': 'button',
        'text': 'Ignore',
    }

    has_releases = Release.objects.filter(
        projects=group.project,
        organization_id=group.project.organization_id
    ).exists()

    if not has_releases:
        resolve_button.update({
            'name': 'status',
            'text': 'Resolve',
            'value': 'resolved',
        })

    if status == GroupStatus.RESOLVED:
        resolve_button.update({
            'name': 'status',
            'text': 'Unresolve',
            'value': 'unresolved',
        })

    if status == GroupStatus.IGNORED:
        ignore_button.update({
            'text': 'Stop Ignoring',
            'value': 'unresolved',
        })

    option_groups = []

    if teams:
        option_groups.append({
            'text': 'Teams',
            'options': teams,
        })

    if members:
        option_groups.append({
            'text': 'People',
            'options': members,
        })

    payload_actions = [
        resolve_button,
        ignore_button,
        {
            'name': 'assign',
            'text': 'Select Assignee...',
            'type': 'select',
            'selected_options': [assignee],
            'option_groups': option_groups,
        },
    ]

    fields = []

    if tags:
        event_tags = event.tags if event else group.get_latest_event().tags

        for key, value in event_tags:
            std_key = tagstore.get_standardized_key(key)
            if std_key not in tags:
                continue

            labeled_value = tagstore.get_tag_value_label(key, value)
            fields.append(
                {
                    'title': std_key.encode('utf-8'),
                    'value': labeled_value.encode('utf-8'),
                    'short': True,
                }
            )

    if actions:
        action_texts = filter(None, [build_action_text(group, identity, a) for a in actions])
        text += '\n' + '\n'.join(action_texts)

        color = ACTIONED_ISSUE_COLOR
        payload_actions = []

    ts = group.last_seen

    if event:
        event_ts = event.datetime
        ts = max(ts, event_ts)

    footer = u'{}'.format(group.qualified_short_id)

    if rules:
        footer += u' via {}'.format(rules[0].label)

        if len(rules) > 1:
            footer += u' (+{} other)'.format(len(rules) - 1)

    return {
        'fallback': u'[{}] {}'.format(group.project.slug, group.title),
        'title': build_attachment_title(group, event),
        'title_link': group.get_absolute_url(params={'referrer': 'slack'}),
        'text': text,
        'fields': fields,
        'mrkdwn_in': ['text'],
        'callback_id': json.dumps({'issue': group.id}),
        'footer_icon': logo_url,
        'footer': footer,
        'ts': to_timestamp(ts),
        'color': color,
        'actions': payload_actions,
    }