Skip to content

Commit

Permalink
v1.9.2
Browse files Browse the repository at this point in the history
  • Loading branch information
joeyorlando authored Aug 19, 2024
2 parents c757401 + 0c96427 commit aa85994
Show file tree
Hide file tree
Showing 15 changed files with 129 additions and 62 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20.16.0
2 changes: 1 addition & 1 deletion docs/sources/manage/notify/ms-teams/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The following is required to connect to Microsoft Teams to Grafana OnCall:

- You must have Admin permissions in your Grafana Cloud instance.
- You must have Owner permissions in Microsoft Teams.
- Install the Grafana OnCall app from the [Microsoft Marketplace](https://appsource.microsoft.com/en-us/product/office/WA200004307).
- Install the Grafana IRM app from the [Microsoft Marketplace](https://appsource.microsoft.com/en-us/product/office/WA200004307).

## Install Microsoft Teams integration for Grafana OnCall

Expand Down
5 changes: 2 additions & 3 deletions engine/apps/public_api/tests/test_alert_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,9 @@ def test_pagination(settings, alert_group_public_api_setup):
token, alert_groups, _, _ = alert_group_public_api_setup
client = APIClient()

url = reverse("api-public:alert_groups-list")
url = "{}?perpage=1".format(reverse("api-public:alert_groups-list"))

with patch("common.api_helpers.paginators.PathPrefixedPagePagination.get_page_size", return_value=1):
response = client.get(url, HTTP_AUTHORIZATION=token)
response = client.get(url, HTTP_AUTHORIZATION=token)

assert response.status_code == status.HTTP_200_OK
result = response.json()
Expand Down
20 changes: 18 additions & 2 deletions engine/apps/telegram/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.conf import settings
from telegram import Bot, InlineKeyboardMarkup, Message, ParseMode
from telegram.error import BadRequest, InvalidToken, Unauthorized
from telegram.error import BadRequest, InvalidToken, TelegramError, Unauthorized
from telegram.utils.request import Request

from apps.alerts.models import AlertGroup
Expand All @@ -27,6 +27,18 @@ def __init__(self, token: Optional[str] = None):
if self.token is None:
raise InvalidToken()

class BadRequestMessage:
CHAT_NOT_FOUND = "Chat not found"
MESSAGE_IS_NOT_MODIFIED = "Message is not modified"
MESSAGE_TO_EDIT_NOT_FOUND = "Message to edit not found"
NEED_ADMIN_RIGHTS_IN_THE_CHANNEL = "Need administrator rights in the channel chat"
MESSAGE_TO_BE_REPLIED_NOT_FOUND = "Message to be replied not found"

class UnauthorizedMessage:
BOT_WAS_BLOCKED_BY_USER = "Forbidden: bot was blocked by the user"
INVALID_TOKEN = "Invalid token"
USER_IS_DEACTIVATED = "Forbidden: user is deactivated"

@property
def api_client(self) -> Bot:
return Bot(self.token, request=Request(read_timeout=15))
Expand Down Expand Up @@ -96,7 +108,7 @@ def send_raw_message(
disable_web_page_preview=False,
)
except BadRequest as e:
logger.warning("Telegram BadRequest: {}".format(e.message))
logger.warning(f"Telegram BadRequest: {e.message}")
raise

return message
Expand Down Expand Up @@ -171,3 +183,7 @@ def _get_message_and_keyboard(
raise Exception(f"_get_message_and_keyboard with type {message_type} is not implemented")

return text, keyboard

@staticmethod
def error_message_is(error: TelegramError, messages: list[str]) -> bool:
return error.message in messages
6 changes: 3 additions & 3 deletions engine/apps/telegram/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def decorated(*args, **kwargs):
try:
return f(*args, **kwargs)
except error.BadRequest as e:
if "Message is not modified" in e.message:
if TelegramClient.error_message_is(e, [TelegramClient.BadRequestMessage.MESSAGE_IS_NOT_MODIFIED]):
logger.warning(
f"Tried to change Telegram message, but update is identical to original message. "
f"args: {args}, kwargs: {kwargs}"
Expand All @@ -59,7 +59,7 @@ def decorated(*args, **kwargs):
try:
return f(*args, **kwargs)
except error.BadRequest as e:
if "Message to edit not found" in e.message:
if TelegramClient.error_message_is(e, [TelegramClient.BadRequestMessage.MESSAGE_TO_EDIT_NOT_FOUND]):
logger.warning(
f"Tried to edit Telegram message, but message was deleted. args: {args}, kwargs: {kwargs}"
)
Expand All @@ -75,7 +75,7 @@ def decorated(*args, **kwargs):
try:
return f(*args, **kwargs)
except error.BadRequest as e:
if "Replied message not found" in e.message:
if TelegramClient.error_message_is(e, [TelegramClient.BadRequestMessage.MESSAGE_TO_BE_REPLIED_NOT_FOUND]):
logger.warning(
f"Tried to reply to Telegram message, but message was deleted. args: {args}, kwargs: {kwargs}"
)
Expand Down
15 changes: 8 additions & 7 deletions engine/apps/telegram/models/connectors/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,16 @@ def send_alert_group_message(self, alert_group: AlertGroup) -> None:
chat_id=self.channel_chat_id, message_type=TelegramMessage.ALERT_GROUP_MESSAGE, alert_group=alert_group
)
except error.BadRequest as e:
if e.message == "Need administrator rights in the channel chat":
if TelegramClient.error_message_is(
e,
[
TelegramClient.BadRequestMessage.NEED_ADMIN_RIGHTS_IN_THE_CHANNEL,
TelegramClient.BadRequestMessage.CHAT_NOT_FOUND,
],
):
logger.warning(
f"Could not send alert group to Telegram channel with id {self.channel_chat_id} "
f"due to lack of admin rights. alert_group {alert_group.pk}"
)
elif e.message == "Chat not found":
logger.warning(
f"Could not send alert group to Telegram channel with id {self.channel_chat_id} "
f"due to 'Chat not found'. alert_group {alert_group.pk}"
f"due to {e.message}. alert_group {alert_group.pk}"
)
else:
telegram_client.send_message(
Expand Down
38 changes: 23 additions & 15 deletions engine/apps/telegram/models/connectors/personal.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ class TelegramToUserConnector(models.Model):
class Meta:
unique_together = (("user", "telegram_chat_id"),)

class NotificationErrorReason:
USER_WAS_DISABLED = "Telegram user was disabled"
INVALID_TOKEN = "Invalid token"
BOT_BLOCKED_BY_USER = "Bot was blocked by the user"
FORMATTING_ERROR_IN_RENDERED_TEMPLATE = (
"Notification sent but there was a formatting error in the rendered template"
)

@classmethod
def notify_user(cls, user: User, alert_group: AlertGroup, notification_policy: UserNotificationPolicy) -> None:
try:
Expand Down Expand Up @@ -98,7 +106,7 @@ def send_full_alert_group(self, alert_group: AlertGroup, notification_policy: Us
self.user,
notification_policy,
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
reason="Invalid token",
reason=self.NotificationErrorReason.INVALID_TOKEN,
)
return

Expand All @@ -123,37 +131,37 @@ def send_full_alert_group(self, alert_group: AlertGroup, notification_policy: Us
self.user,
notification_policy,
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_FORMATTING_ERROR,
reason="Notification sent but there was a formatting error in the rendered template",
reason=self.NotificationErrorReason.FORMATTING_ERROR_IN_RENDERED_TEMPLATE,
)
telegram_client.send_message(
chat_id=self.telegram_chat_id,
message_type=TelegramMessage.FORMATTING_ERROR,
alert_group=alert_group,
)
except error.Unauthorized as e:
if e.message == "Forbidden: bot was blocked by the user":
if TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.BOT_WAS_BLOCKED_BY_USER]):
TelegramToUserConnector.create_telegram_notification_error(
alert_group,
self.user,
notification_policy,
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_BOT_IS_DELETED,
reason="Bot was blocked by the user",
reason=self.NotificationErrorReason.BOT_BLOCKED_BY_USER,
)
elif e.message == "Invalid token":
elif TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.INVALID_TOKEN]):
TelegramToUserConnector.create_telegram_notification_error(
alert_group,
self.user,
notification_policy,
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
reason="Invalid token",
reason=self.NotificationErrorReason.INVALID_TOKEN,
)
elif e.message == "Forbidden: user is deactivated":
elif TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.USER_IS_DEACTIVATED]):
TelegramToUserConnector.create_telegram_notification_error(
alert_group,
self.user,
notification_policy,
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_USER_IS_DEACTIVATED,
reason="Telegram user was disabled",
reason=self.NotificationErrorReason.USER_WAS_DISABLED,
)
else:
raise e
Expand All @@ -175,7 +183,7 @@ def send_link_to_channel_message(self, alert_group: AlertGroup, notification_pol
self.user,
notification_policy,
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
reason="Invalid token",
reason=self.NotificationErrorReason.INVALID_TOKEN,
)
return

Expand All @@ -193,29 +201,29 @@ def send_link_to_channel_message(self, alert_group: AlertGroup, notification_pol
alert_group=alert_group,
)
except error.Unauthorized as e:
if e.message == "Forbidden: bot was blocked by the user":
if TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.BOT_WAS_BLOCKED_BY_USER]):
TelegramToUserConnector.create_telegram_notification_error(
alert_group,
self.user,
notification_policy,
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_BOT_IS_DELETED,
reason="Bot was blocked by the user",
reason=self.NotificationErrorReason.BOT_BLOCKED_BY_USER,
)
elif e.message == "Invalid token":
elif TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.INVALID_TOKEN]):
TelegramToUserConnector.create_telegram_notification_error(
alert_group,
self.user,
notification_policy,
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
reason="Invalid token",
reason=self.NotificationErrorReason.INVALID_TOKEN,
)
elif e.message == "Forbidden: user is deactivated":
elif TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.USER_IS_DEACTIVATED]):
TelegramToUserConnector.create_telegram_notification_error(
alert_group,
self.user,
notification_policy,
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_USER_IS_DEACTIVATED,
reason="Telegram user was disabled",
reason=self.NotificationErrorReason.USER_WAS_DISABLED,
)
else:
raise e
Expand Down
21 changes: 10 additions & 11 deletions engine/apps/telegram/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def edit_message(self, message_pk):
try:
telegram_client.edit_message(message=message)
except error.BadRequest as e:
if "Message is not modified" in e.message:
if TelegramClient.error_message_is(e, [TelegramClient.BadRequestMessage.MESSAGE_IS_NOT_MODIFIED]):
pass
except (error.RetryAfter, error.TimedOut) as e:
countdown = getattr(e, "retry_after", 3)
Expand Down Expand Up @@ -165,20 +165,19 @@ def send_log_and_actions_message(self, channel_chat_id, group_chat_id, channel_m
reply_to_message_id=reply_to_message_id,
)
except error.BadRequest as e:
if e.message == "Chat not found":
if TelegramClient.error_message_is(
e,
[
TelegramClient.BadRequestMessage.CHAT_NOT_FOUND,
TelegramClient.BadRequestMessage.MESSAGE_TO_BE_REPLIED_NOT_FOUND,
],
):
logger.warning(
f"Could not send log and actions messages to Telegram group with id {group_chat_id} "
f"due to 'Chat not found'. alert_group {alert_group.pk}"
f"due to '{e.message}'. alert_group {alert_group.pk}"
)
return
elif e.message == "Message to reply not found":
logger.warning(
f"Could not send log and actions messages to Telegram group with id {group_chat_id} "
f"due to 'Message to reply not found'. alert_group {alert_group.pk}"
)
return
else:
raise
raise


@shared_dedicated_queue_retry_task(
Expand Down
18 changes: 11 additions & 7 deletions engine/apps/telegram/tests/test_personal_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
from apps.telegram.models import TelegramMessage, TelegramToUserConnector


@patch.object(TelegramClient, "send_raw_message", side_effect=error.BadRequest("Replied message not found"))
@patch.object(
TelegramClient,
"send_raw_message",
side_effect=error.BadRequest(TelegramClient.BadRequestMessage.MESSAGE_TO_BE_REPLIED_NOT_FOUND),
)
@pytest.mark.django_db
def test_personal_connector_replied_message_not_found(
mock_send_message,
Expand Down Expand Up @@ -53,19 +57,19 @@ def test_personal_connector_replied_message_not_found(
"side_effect,notification_error_code,reason",
[
(
error.Unauthorized("Forbidden: bot was blocked by the user"),
error.Unauthorized(TelegramClient.UnauthorizedMessage.BOT_WAS_BLOCKED_BY_USER),
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_BOT_IS_DELETED,
"Bot was blocked by the user",
TelegramToUserConnector.NotificationErrorReason.BOT_BLOCKED_BY_USER,
),
(
error.Unauthorized("Invalid token"),
error.Unauthorized(TelegramClient.UnauthorizedMessage.INVALID_TOKEN),
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
"Invalid token",
TelegramToUserConnector.NotificationErrorReason.INVALID_TOKEN,
),
(
error.Unauthorized("Forbidden: user is deactivated"),
error.Unauthorized(TelegramClient.UnauthorizedMessage.USER_IS_DEACTIVATED),
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_USER_IS_DEACTIVATED,
"Telegram user was disabled",
TelegramToUserConnector.NotificationErrorReason.USER_WAS_DISABLED,
),
],
)
Expand Down
6 changes: 4 additions & 2 deletions engine/apps/telegram/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
from apps.telegram.models import TelegramMessage
from apps.telegram.tasks import send_log_and_actions_message

bad_request_error_msg = TelegramClient.BadRequestMessage.MESSAGE_TO_BE_REPLIED_NOT_FOUND

@patch.object(TelegramClient, "send_raw_message", side_effect=error.BadRequest("Message to reply not found"))

@patch.object(TelegramClient, "send_raw_message", side_effect=error.BadRequest(bad_request_error_msg))
@pytest.mark.django_db
def test_send_log_and_actions_replied_message_not_found(
mock_send_message,
Expand Down Expand Up @@ -42,6 +44,6 @@ def test_send_log_and_actions_replied_message_not_found(

expected_msg = (
f"Could not send log and actions messages to Telegram group with id group_chat_id "
f"due to 'Message to reply not found'. alert_group {alert_group.pk}"
f"due to '{bad_request_error_msg}'. alert_group {alert_group.pk}"
)
assert expected_msg in caplog.text
39 changes: 39 additions & 0 deletions engine/common/api_helpers/paginators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import typing

from django.core.paginator import EmptyPage
from rest_framework.pagination import BasePagination, CursorPagination, PageNumberPagination
from rest_framework.response import Response

Expand Down Expand Up @@ -54,6 +55,44 @@ def get_paginated_response_schema(self, schema):
)
return paginated_schema

def paginate_queryset(self, queryset, request, view=None):
request.build_absolute_uri = lambda: create_engine_url(request.get_full_path())
per_page = request.query_params.get(self.page_size_query_param, self.page_size)
try:
per_page = int(per_page)
except ValueError:
per_page = self.page_size

if per_page < 1:
per_page = self.page_size

paginator = self.django_paginator_class(queryset, per_page)
page_number = request.query_params.get(self.page_query_param, 1)
try:
page_number = int(page_number)
except ValueError:
page_number = 1

if page_number < 1:
page_number = 1

try:
self.page = self.get_page(page_number, paginator)
except EmptyPage:
self.page = paginator.page(paginator.num_pages)

if paginator.num_pages > 1 and self.template is not None:
self.display_page_controls = True

self.request = request
return list(self.page)

def get_page(self, page_number, paginator):
try:
return paginator.page(page_number)
except EmptyPage:
return paginator.page(paginator.num_pages)


class PathPrefixedCursorPagination(BasePathPrefixedPagination, CursorPagination):
def get_paginated_response(self, data: PaginatedData) -> Response:
Expand Down
1 change: 0 additions & 1 deletion grafana-plugin/.nvmrc

This file was deleted.

3 changes: 0 additions & 3 deletions grafana-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,6 @@
"webpack-cli": "^5.1.4",
"webpack-livereload-plugin": "^3.0.2"
},
"engines": {
"node": "20.x"
},
"dependencies": {
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^7.0.0",
Expand Down
Loading

0 comments on commit aa85994

Please sign in to comment.