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, }
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))
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
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))
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": [], }
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": [], }
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': [], }
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
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
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], }
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": [], }
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': [], }
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)
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", }
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": [], }
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, }
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
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))
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)
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, }
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))
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": [], }
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]}
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))
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, } )
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
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
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": [], }
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, }
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})
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())
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
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": [], }
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)
def _get_sentry_avatar_url(self): url = '/images/sentry-email-avatar.png' return absolute_uri(get_asset_url('sentry', url))
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, }