prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  User avatars don't show in emails
  At least for comment notifications, the avatar of the user who commented is just a blue box with a question mark regardless of whether they have a custom avatar or the default gravatar. We should check if this is happening for other notifications or if it's just the comment workflow email.
  
  </issue>
  <code>
  [start of src/sentry/notifications/utils/avatar.py]
  1 from __future__ import annotations
  2 
  3 from django.urls import reverse
  4 from django.utils.html import format_html
  5 from django.utils.safestring import SafeString
  6 
  7 from sentry.models.avatars.user_avatar import UserAvatar
  8 from sentry.models.user import User
  9 from sentry.services.hybrid_cloud.user import RpcUser
  10 from sentry.utils.assets import get_asset_url
  11 from sentry.utils.avatar import get_email_avatar
  12 from sentry.utils.http import absolute_uri
  13 
  14 
  15 def get_user_avatar_url(user: User | RpcUser, size: int = 20) -> str:
  16     ident: str
  17     if isinstance(user, User):
  18         try:
  19             avatar = UserAvatar.objects.get(user=user)
  20             ident = avatar.ident
  21         except UserAvatar.DoesNotExist:
  22             return ""
  23     elif user.avatar:
  24         if user.avatar is None:
  25             return ""
  26         ident = user.avatar.ident
  27     else:
  28         return ""
  29 
  30     url = reverse("sentry-user-avatar-url", args=[ident])
  31     if size:
  32         url = f"{url}?s={int(size)}"
  33     return str(absolute_uri(url))
  34 
  35 
  36 def get_sentry_avatar_url() -> str:
  37     url = "/images/sentry-email-avatar.png"
  38     return str(absolute_uri(get_asset_url("sentry", url)))
  39 
  40 
  41 def avatar_as_html(user: User | RpcUser) -> SafeString:
  42     if not user:
  43         return format_html(
  44             '<img class="avatar" src="{}" width="20px" height="20px" />', get_sentry_avatar_url()
  45         )
  46     avatar_type = user.get_avatar_type()
  47     if avatar_type == "upload":
  48         return format_html('<img class="avatar" src="{}" />', get_user_avatar_url(user))
  49     elif avatar_type == "letter_avatar":
  50         return get_email_avatar(user.get_display_name(), user.get_label(), 20, False)
  51     else:
  52         return get_email_avatar(user.get_display_name(), user.get_label(), 20, True)
  53
  [end of src/sentry/notifications/utils/avatar.py]
  [start of src/sentry/notifications/notifications/activity/note.py]
  1 from __future__ import annotations
  2 
  3 from typing import Any, Mapping, Optional
  4 
  5 from sentry.services.hybrid_cloud.actor import RpcActor
  6 from sentry.types.integrations import ExternalProviders
  7 
  8 from .base import GroupActivityNotification
  9 
  10 
  11 class NoteActivityNotification(GroupActivityNotification):
  12     message_builder = "SlackNotificationsMessageBuilder"
  13     metrics_key = "note_activity"
  14     template_path = "sentry/emails/activity/note"
  15 
  16     def get_description(self) -> tuple[str, Optional[str], Mapping[str, Any]]:
  17         # Notes may contain {} characters so we should escape them.
  18         text = str(self.activity.data["text"]).replace("{", "{{").replace("}", "}}")
  19         return text, None, {}
  20 
  21     @property
  22     def title(self) -> str:
  23         if self.user:
  24             author = self.user.get_display_name()
  25         else:
  26             author = "Unknown"
  27         return f"New comment by {author}"
  28 
  29     def get_notification_title(
  30         self, provider: ExternalProviders, context: Mapping[str, Any] | None = None
  31     ) -> str:
  32         return self.title
  33 
  34     def get_message_description(self, recipient: RpcActor, provider: ExternalProviders) -> Any:
  35         return self.get_context()["text_description"]
  36
  [end of src/sentry/notifications/notifications/activity/note.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/src/sentry/notifications/notifications/activity/note.py b/src/sentry/notifications/notifications/activity/note.py
  --- a/src/sentry/notifications/notifications/activity/note.py
  +++ b/src/sentry/notifications/notifications/activity/note.py
  @@ -2,6 +2,10 @@
   
   from typing import Any, Mapping, Optional
   
  +from django.utils.html import format_html
  +from django.utils.safestring import SafeString
  +
  +from sentry.notifications.utils.avatar import avatar_as_html
   from sentry.services.hybrid_cloud.actor import RpcActor
   from sentry.types.integrations import ExternalProviders
   
  @@ -33,3 +37,15 @@
   
       def get_message_description(self, recipient: RpcActor, provider: ExternalProviders) -> Any:
           return self.get_context()["text_description"]
  +
  +    def description_as_html(self, description: str, params: Mapping[str, Any]) -> SafeString:
  +        """Note emails are formatted differently from almost all other activity emails.
  +        Rather than passing the `description` as a string to be formatted into HTML with
  +        `author` and `an_issue` (see base definition and resolved.py's `get_description`
  +        as an example) we are simply passed the comment as a string that needs no formatting,
  +        and want the avatar on it's own rather than bundled with the author's display name
  +        because the display name is already shown in the notification title."""
  +        fmt = '<span class="avatar-container">{}</span>'
  +        if self.user:
  +            return format_html(fmt, avatar_as_html(self.user, 48))
  +        return format_html(description)
  diff --git a/src/sentry/notifications/utils/avatar.py b/src/sentry/notifications/utils/avatar.py
  --- a/src/sentry/notifications/utils/avatar.py
  +++ b/src/sentry/notifications/utils/avatar.py
  @@ -38,15 +38,18 @@
       return str(absolute_uri(get_asset_url("sentry", url)))
   
   
  -def avatar_as_html(user: User | RpcUser) -> SafeString:
  +def avatar_as_html(user: User | RpcUser, size: int = 20) -> SafeString:
       if not user:
           return format_html(
  -            '<img class="avatar" src="{}" width="20px" height="20px" />', get_sentry_avatar_url()
  +            '<img class="avatar" src="{}" width="{}px" height="{}px" />',
  +            get_sentry_avatar_url(),
  +            size,
  +            size,
           )
       avatar_type = user.get_avatar_type()
       if avatar_type == "upload":
           return format_html('<img class="avatar" src="{}" />', get_user_avatar_url(user))
       elif avatar_type == "letter_avatar":
  -        return get_email_avatar(user.get_display_name(), user.get_label(), 20, False)
  +        return get_email_avatar(user.get_display_name(), user.get_label(), size, False)
       else:
  -        return get_email_avatar(user.get_display_name(), user.get_label(), 20, True)
  +        return get_email_avatar(user.get_display_name(), user.get_label(), size, True)
  
verification_info: {"golden_diff": "diff --git a/src/sentry/notifications/notifications/activity/note.py b/src/sentry/notifications/notifications/activity/note.py\n--- a/src/sentry/notifications/notifications/activity/note.py\n+++ b/src/sentry/notifications/notifications/activity/note.py\n@@ -2,6 +2,10 @@\n \n from typing import Any, Mapping, Optional\n \n+from django.utils.html import format_html\n+from django.utils.safestring import SafeString\n+\n+from sentry.notifications.utils.avatar import avatar_as_html\n from sentry.services.hybrid_cloud.actor import RpcActor\n from sentry.types.integrations import ExternalProviders\n \n@@ -33,3 +37,15 @@\n \n     def get_message_description(self, recipient: RpcActor, provider: ExternalProviders) -> Any:\n         return self.get_context()[\"text_description\"]\n+\n+    def description_as_html(self, description: str, params: Mapping[str, Any]) -> SafeString:\n+        \"\"\"Note emails are formatted differently from almost all other activity emails.\n+        Rather than passing the `description` as a string to be formatted into HTML with\n+        `author` and `an_issue` (see base definition and resolved.py's `get_description`\n+        as an example) we are simply passed the comment as a string that needs no formatting,\n+        and want the avatar on it's own rather than bundled with the author's display name\n+        because the display name is already shown in the notification title.\"\"\"\n+        fmt = '<span class=\"avatar-container\">{}</span>'\n+        if self.user:\n+            return format_html(fmt, avatar_as_html(self.user, 48))\n+        return format_html(description)\ndiff --git a/src/sentry/notifications/utils/avatar.py b/src/sentry/notifications/utils/avatar.py\n--- a/src/sentry/notifications/utils/avatar.py\n+++ b/src/sentry/notifications/utils/avatar.py\n@@ -38,15 +38,18 @@\n     return str(absolute_uri(get_asset_url(\"sentry\", url)))\n \n \n-def avatar_as_html(user: User | RpcUser) -> SafeString:\n+def avatar_as_html(user: User | RpcUser, size: int = 20) -> SafeString:\n     if not user:\n         return format_html(\n-            '<img class=\"avatar\" src=\"{}\" width=\"20px\" height=\"20px\" />', get_sentry_avatar_url()\n+            '<img class=\"avatar\" src=\"{}\" width=\"{}px\" height=\"{}px\" />',\n+            get_sentry_avatar_url(),\n+            size,\n+            size,\n         )\n     avatar_type = user.get_avatar_type()\n     if avatar_type == \"upload\":\n         return format_html('<img class=\"avatar\" src=\"{}\" />', get_user_avatar_url(user))\n     elif avatar_type == \"letter_avatar\":\n-        return get_email_avatar(user.get_display_name(), user.get_label(), 20, False)\n+        return get_email_avatar(user.get_display_name(), user.get_label(), size, False)\n     else:\n-        return get_email_avatar(user.get_display_name(), user.get_label(), 20, True)\n+        return get_email_avatar(user.get_display_name(), user.get_label(), size, True)\n", "issue": "User avatars don't show in emails\nAt least for comment notifications, the avatar of the user who commented is just a blue box with a question mark regardless of whether they have a custom avatar or the default gravatar. We should check if this is happening for other notifications or if it's just the comment workflow email.\n", "before_files": [{"content": "from __future__ import annotations\n\nfrom django.urls import reverse\nfrom django.utils.html import format_html\nfrom django.utils.safestring import SafeString\n\nfrom sentry.models.avatars.user_avatar import UserAvatar\nfrom sentry.models.user import User\nfrom sentry.services.hybrid_cloud.user import RpcUser\nfrom sentry.utils.assets import get_asset_url\nfrom sentry.utils.avatar import get_email_avatar\nfrom sentry.utils.http import absolute_uri\n\n\ndef get_user_avatar_url(user: User | RpcUser, size: int = 20) -> str:\n    ident: str\n    if isinstance(user, User):\n        try:\n            avatar = UserAvatar.objects.get(user=user)\n            ident = avatar.ident\n        except UserAvatar.DoesNotExist:\n            return \"\"\n    elif user.avatar:\n        if user.avatar is None:\n            return \"\"\n        ident = user.avatar.ident\n    else:\n        return \"\"\n\n    url = reverse(\"sentry-user-avatar-url\", args=[ident])\n    if size:\n        url = f\"{url}?s={int(size)}\"\n    return str(absolute_uri(url))\n\n\ndef get_sentry_avatar_url() -> str:\n    url = \"/images/sentry-email-avatar.png\"\n    return str(absolute_uri(get_asset_url(\"sentry\", url)))\n\n\ndef avatar_as_html(user: User | RpcUser) -> SafeString:\n    if not user:\n        return format_html(\n            '<img class=\"avatar\" src=\"{}\" width=\"20px\" height=\"20px\" />', get_sentry_avatar_url()\n        )\n    avatar_type = user.get_avatar_type()\n    if avatar_type == \"upload\":\n        return format_html('<img class=\"avatar\" src=\"{}\" />', get_user_avatar_url(user))\n    elif avatar_type == \"letter_avatar\":\n        return get_email_avatar(user.get_display_name(), user.get_label(), 20, False)\n    else:\n        return get_email_avatar(user.get_display_name(), user.get_label(), 20, True)\n", "path": "src/sentry/notifications/utils/avatar.py"}, {"content": "from __future__ import annotations\n\nfrom typing import Any, Mapping, Optional\n\nfrom sentry.services.hybrid_cloud.actor import RpcActor\nfrom sentry.types.integrations import ExternalProviders\n\nfrom .base import GroupActivityNotification\n\n\nclass NoteActivityNotification(GroupActivityNotification):\n    message_builder = \"SlackNotificationsMessageBuilder\"\n    metrics_key = \"note_activity\"\n    template_path = \"sentry/emails/activity/note\"\n\n    def get_description(self) -> tuple[str, Optional[str], Mapping[str, Any]]:\n        # Notes may contain {} characters so we should escape them.\n        text = str(self.activity.data[\"text\"]).replace(\"{\", \"{{\").replace(\"}\", \"}}\")\n        return text, None, {}\n\n    @property\n    def title(self) -> str:\n        if self.user:\n            author = self.user.get_display_name()\n        else:\n            author = \"Unknown\"\n        return f\"New comment by {author}\"\n\n    def get_notification_title(\n        self, provider: ExternalProviders, context: Mapping[str, Any] | None = None\n    ) -> str:\n        return self.title\n\n    def get_message_description(self, recipient: RpcActor, provider: ExternalProviders) -> Any:\n        return self.get_context()[\"text_description\"]\n", "path": "src/sentry/notifications/notifications/activity/note.py"}]}