We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Slack alert message doesn't match up with why alert was actually triggered
### Environment

SaaS (https://sentry.io/)

### Steps to Reproduce

Hi Team,

A customer reported to us that the messages in their Slack alerts doesn't match up with the real reason the alert was triggered (displayed correctly once you click into the alert and go to the alert page). I was able to replicate this by setting up a similar type of alert.
1. Set up a metric alert to trigger when `Number of errors is X% higher in Y minutes compared to same time one week ago`
2. Alert action is to be notified via Slack, specifically a Slack channel
3. Receive Slack alert
<img width="1269" alt="Screenshot 2023-05-04 at 3 45 03 PM" src="https://user-images.githubusercontent.com/98921471/236560084-f64cf7cf-7856-4921-8c3d-ca70433afda8.png">


### Expected Result

When the alert triggers, I expect the message in Slack to say: "157 events in the last 10 minutes". This is verified by the alert details of why the alert triggered.

Alternatively, it can also be expected to say: "Number of errors is 89.16% higher in 10 minutes compared to the same time a week ago".
<img width="1277" alt="Screenshot 2023-05-04 at 3 45 14 PM" src="https://user-images.githubusercontent.com/98921471/236560515-38e4361c-0c8e-4c53-8986-fcd5a67f40fe.png">


### Actual Result

The Slack alert message says "168.478260.. events in the last 10 minutes". This doesn't match up with the 157 events that I see in the alert details.

For the customer, it said "400.0 events in the last 60 minutes" when it should've said "4 events in the last 60 minutes", which is 2 orders of magnitudes off.
<img width="544" alt="Screenshot 2023-05-04 at 3 44 30 PM" src="https://user-images.githubusercontent.com/98921471/236560823-894e8da5-6918-416c-8f65-f182456424d7.png">


### Product Area

Alerts

### Link

_No response_

### DSN

_No response_

### Version

_No response_

┆Issue is synchronized with this [Jira Improvement](https://getsentry.atlassian.net/browse/FEEDBACK-1825) by [Unito](https://www.unito.io)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/sentry/integrations/metric_alerts.py`
Content:
```
1 from datetime import timedelta
2 from typing import Optional
3 from urllib import parse
4 
5 from django.db.models import Max
6 from django.urls import reverse
7 from django.utils.translation import gettext as _
8 
9 from sentry.constants import CRASH_RATE_ALERT_AGGREGATE_ALIAS
10 from sentry.incidents.logic import get_incident_aggregates
11 from sentry.incidents.models import (
12     INCIDENT_STATUS,
13     AlertRule,
14     Incident,
15     IncidentStatus,
16     IncidentTrigger,
17 )
18 from sentry.utils.assets import get_asset_url
19 from sentry.utils.http import absolute_uri
20 
21 QUERY_AGGREGATION_DISPLAY = {
22     "count()": "events",
23     "count_unique(tags[sentry:user])": "users affected",
24     "percentage(sessions_crashed, sessions)": "% sessions crash free rate",
25     "percentage(users_crashed, users)": "% users crash free rate",
26 }
27 LOGO_URL = absolute_uri(get_asset_url("sentry", "images/sentry-email-avatar.png"))
28 
29 
30 def get_metric_count_from_incident(incident: Incident) -> str:
31     """Returns the current or last count of an incident aggregate."""
32     incident_trigger = (
33         IncidentTrigger.objects.filter(incident=incident).order_by("-date_modified").first()
34     )
35     if incident_trigger:
36         alert_rule_trigger = incident_trigger.alert_rule_trigger
37         # TODO: If we're relying on this and expecting possible delays between a
38         # trigger fired and this function running, then this could actually be
39         # incorrect if they changed the trigger's time window in this time period.
40         # Should we store it?
41         start = incident_trigger.date_modified - timedelta(
42             seconds=alert_rule_trigger.alert_rule.snuba_query.time_window
43         )
44         end = incident_trigger.date_modified
45     else:
46         start, end = None, None
47 
48     return get_incident_aggregates(incident=incident, start=start, end=end).get("count")
49 
50 
51 def get_incident_status_text(alert_rule: AlertRule, metric_value: str) -> str:
52     """Returns a human readable current status of an incident"""
53     agg_display_key = alert_rule.snuba_query.aggregate
54 
55     if CRASH_RATE_ALERT_AGGREGATE_ALIAS in alert_rule.snuba_query.aggregate:
56         agg_display_key = agg_display_key.split(f"AS {CRASH_RATE_ALERT_AGGREGATE_ALIAS}")[0].strip()
57 
58     agg_text = QUERY_AGGREGATION_DISPLAY.get(agg_display_key, alert_rule.snuba_query.aggregate)
59 
60     if agg_text.startswith("%"):
61         if metric_value is not None:
62             metric_and_agg_text = f"{metric_value}{agg_text}"
63         else:
64             metric_and_agg_text = f"No{agg_text[1:]}"
65     else:
66         metric_and_agg_text = f"{metric_value} {agg_text}"
67 
68     time_window = alert_rule.snuba_query.time_window // 60
69     interval = "minute" if time_window == 1 else "minutes"
70     text = _("%(metric_and_agg_text)s in the last %(time_window)d %(interval)s") % {
71         "metric_and_agg_text": metric_and_agg_text,
72         "time_window": time_window,
73         "interval": interval,
74     }
75 
76     return text
77 
78 
79 def incident_attachment_info(incident, new_status: IncidentStatus, metric_value=None):
80     alert_rule = incident.alert_rule
81 
82     status = INCIDENT_STATUS[new_status]
83 
84     if metric_value is None:
85         metric_value = get_metric_count_from_incident(incident)
86 
87     text = get_incident_status_text(alert_rule, metric_value)
88     title = f"{status}: {alert_rule.name}"
89 
90     title_link = alert_rule.organization.absolute_url(
91         reverse(
92             "sentry-metric-alert-details",
93             kwargs={
94                 "organization_slug": alert_rule.organization.slug,
95                 "alert_rule_id": alert_rule.id,
96             },
97         ),
98         query=parse.urlencode({"alert": str(incident.identifier)}),
99     )
100 
101     return {
102         "title": title,
103         "text": text,
104         "logo_url": LOGO_URL,
105         "status": status,
106         "ts": incident.date_started,
107         "title_link": title_link,
108     }
109 
110 
111 def metric_alert_attachment_info(
112     alert_rule: AlertRule,
113     selected_incident: Optional[Incident] = None,
114     new_status: Optional[IncidentStatus] = None,
115     metric_value: Optional[str] = None,
116 ):
117     latest_incident = None
118     if selected_incident is None:
119         try:
120             # Use .get() instead of .first() to avoid sorting table by id
121             latest_incident = Incident.objects.filter(
122                 id__in=Incident.objects.filter(alert_rule=alert_rule)
123                 .values("alert_rule_id")
124                 .annotate(incident_id=Max("id"))
125                 .values("incident_id")
126             ).get()
127         except Incident.DoesNotExist:
128             latest_incident = None
129 
130     if new_status:
131         status = INCIDENT_STATUS[new_status]
132     elif selected_incident:
133         status = INCIDENT_STATUS[IncidentStatus(selected_incident.status)]
134     elif latest_incident:
135         status = INCIDENT_STATUS[IncidentStatus(latest_incident.status)]
136     else:
137         status = INCIDENT_STATUS[IncidentStatus.CLOSED]
138 
139     query = None
140     if selected_incident:
141         query = parse.urlencode({"alert": str(selected_incident.identifier)})
142     title = f"{status}: {alert_rule.name}"
143     title_link = alert_rule.organization.absolute_url(
144         reverse(
145             "sentry-metric-alert-details",
146             kwargs={
147                 "organization_slug": alert_rule.organization.slug,
148                 "alert_rule_id": alert_rule.id,
149             },
150         ),
151         query=query,
152     )
153 
154     if metric_value is None:
155         if (
156             selected_incident is None
157             and latest_incident
158             and latest_incident.status != IncidentStatus.CLOSED
159         ):
160             # Without a selected incident, use latest incident if it is not resolved
161             incident_info = latest_incident
162         else:
163             incident_info = selected_incident
164 
165         if incident_info:
166             metric_value = get_metric_count_from_incident(incident_info)
167 
168     text = ""
169     if metric_value is not None and status != INCIDENT_STATUS[IncidentStatus.CLOSED]:
170         text = get_incident_status_text(alert_rule, metric_value)
171 
172     date_started = None
173     if selected_incident:
174         date_started = selected_incident.date_started
175 
176     last_triggered_date = None
177     if latest_incident:
178         last_triggered_date = latest_incident.date_started
179 
180     return {
181         "title": title,
182         "text": text,
183         "logo_url": LOGO_URL,
184         "status": status,
185         "date_started": date_started,
186         "last_triggered_date": last_triggered_date,
187         "title_link": title_link,
188     }
189
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/sentry/integrations/metric_alerts.py b/src/sentry/integrations/metric_alerts.py
--- a/src/sentry/integrations/metric_alerts.py
+++ b/src/sentry/integrations/metric_alerts.py
@@ -11,6 +11,7 @@
 from sentry.incidents.models import (
     INCIDENT_STATUS,
     AlertRule,
+    AlertRuleThresholdType,
     Incident,
     IncidentStatus,
     IncidentTrigger,
@@ -25,6 +26,16 @@
     "percentage(users_crashed, users)": "% users crash free rate",
 }
 LOGO_URL = absolute_uri(get_asset_url("sentry", "images/sentry-email-avatar.png"))
+# These should be the same as the options in the frontend
+# COMPARISON_DELTA_OPTIONS
+TEXT_COMPARISON_DELTA = {
+    5: ("same time 5 minutes ago"),  # 5 minutes
+    15: ("same time 15 minutes ago"),  # 15 minutes
+    60: ("same time one hour ago"),  # one hour
+    1440: ("same time one day ago"),  # one day
+    10080: ("same time one week ago"),  # one week
+    43200: ("same time one month ago"),  # 30 days
+}
 
 
 def get_metric_count_from_incident(incident: Incident) -> str:
@@ -67,14 +78,27 @@
 
     time_window = alert_rule.snuba_query.time_window // 60
     interval = "minute" if time_window == 1 else "minutes"
-    text = _("%(metric_and_agg_text)s in the last %(time_window)d %(interval)s") % {
+    # % change alerts have a comparison delta
+    if alert_rule.comparison_delta:
+        metric_and_agg_text = f"{agg_text.capitalize()} {int(metric_value)}%"
+        higher_or_lower = (
+            "higher" if alert_rule.threshold_type == AlertRuleThresholdType.ABOVE.value else "lower"
+        )
+        comparison_delta_minutes = alert_rule.comparison_delta // 60
+        comparison_string = TEXT_COMPARISON_DELTA.get(
+            comparison_delta_minutes, f"same time {comparison_delta_minutes} minutes ago"
+        )
+        return _(
+            f"{metric_and_agg_text} {higher_or_lower} in the last {time_window} {interval} "
+            f"compared to the {comparison_string}"
+        )
+
+    return _("%(metric_and_agg_text)s in the last %(time_window)d %(interval)s") % {
         "metric_and_agg_text": metric_and_agg_text,
         "time_window": time_window,
         "interval": interval,
     }
 
-    return text
-
 
 def incident_attachment_info(incident, new_status: IncidentStatus, metric_value=None):
     alert_rule = incident.alert_rule

{"golden_diff": "diff --git a/src/sentry/integrations/metric_alerts.py b/src/sentry/integrations/metric_alerts.py\n--- a/src/sentry/integrations/metric_alerts.py\n+++ b/src/sentry/integrations/metric_alerts.py\n@@ -11,6 +11,7 @@\n from sentry.incidents.models import (\n     INCIDENT_STATUS,\n     AlertRule,\n+    AlertRuleThresholdType,\n     Incident,\n     IncidentStatus,\n     IncidentTrigger,\n@@ -25,6 +26,16 @@\n     \"percentage(users_crashed, users)\": \"% users crash free rate\",\n }\n LOGO_URL = absolute_uri(get_asset_url(\"sentry\", \"images/sentry-email-avatar.png\"))\n+# These should be the same as the options in the frontend\n+# COMPARISON_DELTA_OPTIONS\n+TEXT_COMPARISON_DELTA = {\n+    5: (\"same time 5 minutes ago\"),  # 5 minutes\n+    15: (\"same time 15 minutes ago\"),  # 15 minutes\n+    60: (\"same time one hour ago\"),  # one hour\n+    1440: (\"same time one day ago\"),  # one day\n+    10080: (\"same time one week ago\"),  # one week\n+    43200: (\"same time one month ago\"),  # 30 days\n+}\n \n \n def get_metric_count_from_incident(incident: Incident) -> str:\n@@ -67,14 +78,27 @@\n \n     time_window = alert_rule.snuba_query.time_window // 60\n     interval = \"minute\" if time_window == 1 else \"minutes\"\n-    text = _(\"%(metric_and_agg_text)s in the last %(time_window)d %(interval)s\") % {\n+    # % change alerts have a comparison delta\n+    if alert_rule.comparison_delta:\n+        metric_and_agg_text = f\"{agg_text.capitalize()} {int(metric_value)}%\"\n+        higher_or_lower = (\n+            \"higher\" if alert_rule.threshold_type == AlertRuleThresholdType.ABOVE.value else \"lower\"\n+        )\n+        comparison_delta_minutes = alert_rule.comparison_delta // 60\n+        comparison_string = TEXT_COMPARISON_DELTA.get(\n+            comparison_delta_minutes, f\"same time {comparison_delta_minutes} minutes ago\"\n+        )\n+        return _(\n+            f\"{metric_and_agg_text} {higher_or_lower} in the last {time_window} {interval} \"\n+            f\"compared to the {comparison_string}\"\n+        )\n+\n+    return _(\"%(metric_and_agg_text)s in the last %(time_window)d %(interval)s\") % {\n         \"metric_and_agg_text\": metric_and_agg_text,\n         \"time_window\": time_window,\n         \"interval\": interval,\n     }\n \n-    return text\n-\n \n def incident_attachment_info(incident, new_status: IncidentStatus, metric_value=None):\n     alert_rule = incident.alert_rule\n", "issue": "Slack alert message doesn't match up with why alert was actually triggered\n### Environment\n\nSaaS (https://sentry.io/)\n\n### Steps to Reproduce\n\nHi Team,\n\nA customer reported to us that the messages in their Slack alerts doesn't match up with the real reason the alert was triggered (displayed correctly once you click into the alert and go to the alert page). I was able to replicate this by setting up a similar type of alert.\n1. Set up a metric alert to trigger when `Number of errors is X% higher in Y minutes compared to same time one week ago`\n2. Alert action is to be notified via Slack, specifically a Slack channel\n3. Receive Slack alert\n<img width=\"1269\" alt=\"Screenshot 2023-05-04 at 3 45 03 PM\" src=\"https://user-images.githubusercontent.com/98921471/236560084-f64cf7cf-7856-4921-8c3d-ca70433afda8.png\">\n\n\n### Expected Result\n\nWhen the alert triggers, I expect the message in Slack to say: \"157 events in the last 10 minutes\". This is verified by the alert details of why the alert triggered.\n\nAlternatively, it can also be expected to say: \"Number of errors is 89.16% higher in 10 minutes compared to the same time a week ago\".\n<img width=\"1277\" alt=\"Screenshot 2023-05-04 at 3 45 14 PM\" src=\"https://user-images.githubusercontent.com/98921471/236560515-38e4361c-0c8e-4c53-8986-fcd5a67f40fe.png\">\n\n\n### Actual Result\n\nThe Slack alert message says \"168.478260.. events in the last 10 minutes\". This doesn't match up with the 157 events that I see in the alert details.\n\nFor the customer, it said \"400.0 events in the last 60 minutes\" when it should've said \"4 events in the last 60 minutes\", which is 2 orders of magnitudes off.\n<img width=\"544\" alt=\"Screenshot 2023-05-04 at 3 44 30 PM\" src=\"https://user-images.githubusercontent.com/98921471/236560823-894e8da5-6918-416c-8f65-f182456424d7.png\">\n\n\n### Product Area\n\nAlerts\n\n### Link\n\n_No response_\n\n### DSN\n\n_No response_\n\n### Version\n\n_No response_\n\n\u2506Issue is synchronized with this [Jira Improvement](https://getsentry.atlassian.net/browse/FEEDBACK-1825) by [Unito](https://www.unito.io)\n\n", "before_files": [{"content": "from datetime import timedelta\nfrom typing import Optional\nfrom urllib import parse\n\nfrom django.db.models import Max\nfrom django.urls import reverse\nfrom django.utils.translation import gettext as _\n\nfrom sentry.constants import CRASH_RATE_ALERT_AGGREGATE_ALIAS\nfrom sentry.incidents.logic import get_incident_aggregates\nfrom sentry.incidents.models import (\n    INCIDENT_STATUS,\n    AlertRule,\n    Incident,\n    IncidentStatus,\n    IncidentTrigger,\n)\nfrom sentry.utils.assets import get_asset_url\nfrom sentry.utils.http import absolute_uri\n\nQUERY_AGGREGATION_DISPLAY = {\n    \"count()\": \"events\",\n    \"count_unique(tags[sentry:user])\": \"users affected\",\n    \"percentage(sessions_crashed, sessions)\": \"% sessions crash free rate\",\n    \"percentage(users_crashed, users)\": \"% users crash free rate\",\n}\nLOGO_URL = absolute_uri(get_asset_url(\"sentry\", \"images/sentry-email-avatar.png\"))\n\n\ndef get_metric_count_from_incident(incident: Incident) -> str:\n    \"\"\"Returns the current or last count of an incident aggregate.\"\"\"\n    incident_trigger = (\n        IncidentTrigger.objects.filter(incident=incident).order_by(\"-date_modified\").first()\n    )\n    if incident_trigger:\n        alert_rule_trigger = incident_trigger.alert_rule_trigger\n        # TODO: If we're relying on this and expecting possible delays between a\n        # trigger fired and this function running, then this could actually be\n        # incorrect if they changed the trigger's time window in this time period.\n        # Should we store it?\n        start = incident_trigger.date_modified - timedelta(\n            seconds=alert_rule_trigger.alert_rule.snuba_query.time_window\n        )\n        end = incident_trigger.date_modified\n    else:\n        start, end = None, None\n\n    return get_incident_aggregates(incident=incident, start=start, end=end).get(\"count\")\n\n\ndef get_incident_status_text(alert_rule: AlertRule, metric_value: str) -> str:\n    \"\"\"Returns a human readable current status of an incident\"\"\"\n    agg_display_key = alert_rule.snuba_query.aggregate\n\n    if CRASH_RATE_ALERT_AGGREGATE_ALIAS in alert_rule.snuba_query.aggregate:\n        agg_display_key = agg_display_key.split(f\"AS {CRASH_RATE_ALERT_AGGREGATE_ALIAS}\")[0].strip()\n\n    agg_text = QUERY_AGGREGATION_DISPLAY.get(agg_display_key, alert_rule.snuba_query.aggregate)\n\n    if agg_text.startswith(\"%\"):\n        if metric_value is not None:\n            metric_and_agg_text = f\"{metric_value}{agg_text}\"\n        else:\n            metric_and_agg_text = f\"No{agg_text[1:]}\"\n    else:\n        metric_and_agg_text = f\"{metric_value} {agg_text}\"\n\n    time_window = alert_rule.snuba_query.time_window // 60\n    interval = \"minute\" if time_window == 1 else \"minutes\"\n    text = _(\"%(metric_and_agg_text)s in the last %(time_window)d %(interval)s\") % {\n        \"metric_and_agg_text\": metric_and_agg_text,\n        \"time_window\": time_window,\n        \"interval\": interval,\n    }\n\n    return text\n\n\ndef incident_attachment_info(incident, new_status: IncidentStatus, metric_value=None):\n    alert_rule = incident.alert_rule\n\n    status = INCIDENT_STATUS[new_status]\n\n    if metric_value is None:\n        metric_value = get_metric_count_from_incident(incident)\n\n    text = get_incident_status_text(alert_rule, metric_value)\n    title = f\"{status}: {alert_rule.name}\"\n\n    title_link = alert_rule.organization.absolute_url(\n        reverse(\n            \"sentry-metric-alert-details\",\n            kwargs={\n                \"organization_slug\": alert_rule.organization.slug,\n                \"alert_rule_id\": alert_rule.id,\n            },\n        ),\n        query=parse.urlencode({\"alert\": str(incident.identifier)}),\n    )\n\n    return {\n        \"title\": title,\n        \"text\": text,\n        \"logo_url\": LOGO_URL,\n        \"status\": status,\n        \"ts\": incident.date_started,\n        \"title_link\": title_link,\n    }\n\n\ndef metric_alert_attachment_info(\n    alert_rule: AlertRule,\n    selected_incident: Optional[Incident] = None,\n    new_status: Optional[IncidentStatus] = None,\n    metric_value: Optional[str] = None,\n):\n    latest_incident = None\n    if selected_incident is None:\n        try:\n            # Use .get() instead of .first() to avoid sorting table by id\n            latest_incident = Incident.objects.filter(\n                id__in=Incident.objects.filter(alert_rule=alert_rule)\n                .values(\"alert_rule_id\")\n                .annotate(incident_id=Max(\"id\"))\n                .values(\"incident_id\")\n            ).get()\n        except Incident.DoesNotExist:\n            latest_incident = None\n\n    if new_status:\n        status = INCIDENT_STATUS[new_status]\n    elif selected_incident:\n        status = INCIDENT_STATUS[IncidentStatus(selected_incident.status)]\n    elif latest_incident:\n        status = INCIDENT_STATUS[IncidentStatus(latest_incident.status)]\n    else:\n        status = INCIDENT_STATUS[IncidentStatus.CLOSED]\n\n    query = None\n    if selected_incident:\n        query = parse.urlencode({\"alert\": str(selected_incident.identifier)})\n    title = f\"{status}: {alert_rule.name}\"\n    title_link = alert_rule.organization.absolute_url(\n        reverse(\n            \"sentry-metric-alert-details\",\n            kwargs={\n                \"organization_slug\": alert_rule.organization.slug,\n                \"alert_rule_id\": alert_rule.id,\n            },\n        ),\n        query=query,\n    )\n\n    if metric_value is None:\n        if (\n            selected_incident is None\n            and latest_incident\n            and latest_incident.status != IncidentStatus.CLOSED\n        ):\n            # Without a selected incident, use latest incident if it is not resolved\n            incident_info = latest_incident\n        else:\n            incident_info = selected_incident\n\n        if incident_info:\n            metric_value = get_metric_count_from_incident(incident_info)\n\n    text = \"\"\n    if metric_value is not None and status != INCIDENT_STATUS[IncidentStatus.CLOSED]:\n        text = get_incident_status_text(alert_rule, metric_value)\n\n    date_started = None\n    if selected_incident:\n        date_started = selected_incident.date_started\n\n    last_triggered_date = None\n    if latest_incident:\n        last_triggered_date = latest_incident.date_started\n\n    return {\n        \"title\": title,\n        \"text\": text,\n        \"logo_url\": LOGO_URL,\n        \"status\": status,\n        \"date_started\": date_started,\n        \"last_triggered_date\": last_triggered_date,\n        \"title_link\": title_link,\n    }\n", "path": "src/sentry/integrations/metric_alerts.py"}], "after_files": [{"content": "from datetime import timedelta\nfrom typing import Optional\nfrom urllib import parse\n\nfrom django.db.models import Max\nfrom django.urls import reverse\nfrom django.utils.translation import gettext as _\n\nfrom sentry.constants import CRASH_RATE_ALERT_AGGREGATE_ALIAS\nfrom sentry.incidents.logic import get_incident_aggregates\nfrom sentry.incidents.models import (\n    INCIDENT_STATUS,\n    AlertRule,\n    AlertRuleThresholdType,\n    Incident,\n    IncidentStatus,\n    IncidentTrigger,\n)\nfrom sentry.utils.assets import get_asset_url\nfrom sentry.utils.http import absolute_uri\n\nQUERY_AGGREGATION_DISPLAY = {\n    \"count()\": \"events\",\n    \"count_unique(tags[sentry:user])\": \"users affected\",\n    \"percentage(sessions_crashed, sessions)\": \"% sessions crash free rate\",\n    \"percentage(users_crashed, users)\": \"% users crash free rate\",\n}\nLOGO_URL = absolute_uri(get_asset_url(\"sentry\", \"images/sentry-email-avatar.png\"))\n# These should be the same as the options in the frontend\n# COMPARISON_DELTA_OPTIONS\nTEXT_COMPARISON_DELTA = {\n    5: (\"same time 5 minutes ago\"),  # 5 minutes\n    15: (\"same time 15 minutes ago\"),  # 15 minutes\n    60: (\"same time one hour ago\"),  # one hour\n    1440: (\"same time one day ago\"),  # one day\n    10080: (\"same time one week ago\"),  # one week\n    43200: (\"same time one month ago\"),  # 30 days\n}\n\n\ndef get_metric_count_from_incident(incident: Incident) -> str:\n    \"\"\"Returns the current or last count of an incident aggregate.\"\"\"\n    incident_trigger = (\n        IncidentTrigger.objects.filter(incident=incident).order_by(\"-date_modified\").first()\n    )\n    if incident_trigger:\n        alert_rule_trigger = incident_trigger.alert_rule_trigger\n        # TODO: If we're relying on this and expecting possible delays between a\n        # trigger fired and this function running, then this could actually be\n        # incorrect if they changed the trigger's time window in this time period.\n        # Should we store it?\n        start = incident_trigger.date_modified - timedelta(\n            seconds=alert_rule_trigger.alert_rule.snuba_query.time_window\n        )\n        end = incident_trigger.date_modified\n    else:\n        start, end = None, None\n\n    return get_incident_aggregates(incident=incident, start=start, end=end).get(\"count\")\n\n\ndef get_incident_status_text(alert_rule: AlertRule, metric_value: str) -> str:\n    \"\"\"Returns a human readable current status of an incident\"\"\"\n    agg_display_key = alert_rule.snuba_query.aggregate\n\n    if CRASH_RATE_ALERT_AGGREGATE_ALIAS in alert_rule.snuba_query.aggregate:\n        agg_display_key = agg_display_key.split(f\"AS {CRASH_RATE_ALERT_AGGREGATE_ALIAS}\")[0].strip()\n\n    agg_text = QUERY_AGGREGATION_DISPLAY.get(agg_display_key, alert_rule.snuba_query.aggregate)\n\n    if agg_text.startswith(\"%\"):\n        if metric_value is not None:\n            metric_and_agg_text = f\"{metric_value}{agg_text}\"\n        else:\n            metric_and_agg_text = f\"No{agg_text[1:]}\"\n    else:\n        metric_and_agg_text = f\"{metric_value} {agg_text}\"\n\n    time_window = alert_rule.snuba_query.time_window // 60\n    interval = \"minute\" if time_window == 1 else \"minutes\"\n    # % change alerts have a comparison delta\n    if alert_rule.comparison_delta:\n        metric_and_agg_text = f\"{agg_text.capitalize()} {int(metric_value)}%\"\n        higher_or_lower = (\n            \"higher\" if alert_rule.threshold_type == AlertRuleThresholdType.ABOVE.value else \"lower\"\n        )\n        comparison_delta_minutes = alert_rule.comparison_delta // 60\n        comparison_string = TEXT_COMPARISON_DELTA.get(\n            comparison_delta_minutes, f\"same time {comparison_delta_minutes} minutes ago\"\n        )\n        return _(\n            f\"{metric_and_agg_text} {higher_or_lower} in the last {time_window} {interval} \"\n            f\"compared to the {comparison_string}\"\n        )\n\n    return _(\"%(metric_and_agg_text)s in the last %(time_window)d %(interval)s\") % {\n        \"metric_and_agg_text\": metric_and_agg_text,\n        \"time_window\": time_window,\n        \"interval\": interval,\n    }\n\n\ndef incident_attachment_info(incident, new_status: IncidentStatus, metric_value=None):\n    alert_rule = incident.alert_rule\n\n    status = INCIDENT_STATUS[new_status]\n\n    if metric_value is None:\n        metric_value = get_metric_count_from_incident(incident)\n\n    text = get_incident_status_text(alert_rule, metric_value)\n    title = f\"{status}: {alert_rule.name}\"\n\n    title_link = alert_rule.organization.absolute_url(\n        reverse(\n            \"sentry-metric-alert-details\",\n            kwargs={\n                \"organization_slug\": alert_rule.organization.slug,\n                \"alert_rule_id\": alert_rule.id,\n            },\n        ),\n        query=parse.urlencode({\"alert\": str(incident.identifier)}),\n    )\n\n    return {\n        \"title\": title,\n        \"text\": text,\n        \"logo_url\": LOGO_URL,\n        \"status\": status,\n        \"ts\": incident.date_started,\n        \"title_link\": title_link,\n    }\n\n\ndef metric_alert_attachment_info(\n    alert_rule: AlertRule,\n    selected_incident: Optional[Incident] = None,\n    new_status: Optional[IncidentStatus] = None,\n    metric_value: Optional[str] = None,\n):\n    latest_incident = None\n    if selected_incident is None:\n        try:\n            # Use .get() instead of .first() to avoid sorting table by id\n            latest_incident = Incident.objects.filter(\n                id__in=Incident.objects.filter(alert_rule=alert_rule)\n                .values(\"alert_rule_id\")\n                .annotate(incident_id=Max(\"id\"))\n                .values(\"incident_id\")\n            ).get()\n        except Incident.DoesNotExist:\n            latest_incident = None\n\n    if new_status:\n        status = INCIDENT_STATUS[new_status]\n    elif selected_incident:\n        status = INCIDENT_STATUS[IncidentStatus(selected_incident.status)]\n    elif latest_incident:\n        status = INCIDENT_STATUS[IncidentStatus(latest_incident.status)]\n    else:\n        status = INCIDENT_STATUS[IncidentStatus.CLOSED]\n\n    query = None\n    if selected_incident:\n        query = parse.urlencode({\"alert\": str(selected_incident.identifier)})\n    title = f\"{status}: {alert_rule.name}\"\n    title_link = alert_rule.organization.absolute_url(\n        reverse(\n            \"sentry-metric-alert-details\",\n            kwargs={\n                \"organization_slug\": alert_rule.organization.slug,\n                \"alert_rule_id\": alert_rule.id,\n            },\n        ),\n        query=query,\n    )\n\n    if metric_value is None:\n        if (\n            selected_incident is None\n            and latest_incident\n            and latest_incident.status != IncidentStatus.CLOSED\n        ):\n            # Without a selected incident, use latest incident if it is not resolved\n            incident_info = latest_incident\n        else:\n            incident_info = selected_incident\n\n        if incident_info:\n            metric_value = get_metric_count_from_incident(incident_info)\n\n    text = \"\"\n    if metric_value is not None and status != INCIDENT_STATUS[IncidentStatus.CLOSED]:\n        text = get_incident_status_text(alert_rule, metric_value)\n\n    date_started = None\n    if selected_incident:\n        date_started = selected_incident.date_started\n\n    last_triggered_date = None\n    if latest_incident:\n        last_triggered_date = latest_incident.date_started\n\n    return {\n        \"title\": title,\n        \"text\": text,\n        \"logo_url\": LOGO_URL,\n        \"status\": status,\n        \"date_started\": date_started,\n        \"last_triggered_date\": last_triggered_date,\n        \"title_link\": title_link,\n    }\n", "path": "src/sentry/integrations/metric_alerts.py"}]}