Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.4.6 #4330

Merged
merged 5 commits into from
May 9, 2024
Merged

v1.4.6 #4330

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions engine/apps/alerts/models/alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,17 +511,8 @@ def slack_app_link(self) -> typing.Optional[str]:

@property
def telegram_permalink(self) -> typing.Optional[str]:
"""
This property will attempt to access an attribute, `prefetched_telegram_messages`, representing a list of
prefetched telegram messages. If this attribute does not exist, it falls back to performing a query.

See `apps.public_api.serializers.incidents.IncidentSerializer.PREFETCH_RELATED` as an example.
"""
from apps.telegram.models.message import TelegramMessage

if hasattr(self, "prefetched_telegram_messages"):
return self.prefetched_telegram_messages[0].link if self.prefetched_telegram_messages else None

main_telegram_message = self.telegram_messages.filter(
chat_id__startswith="-", message_type=TelegramMessage.ALERT_GROUP_MESSAGE
).first()
Expand Down
35 changes: 23 additions & 12 deletions engine/apps/google/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.conf import settings
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

from apps.google import constants, utils
from apps.google.types import GoogleCalendarEvent as GoogleCalendarEventType
Expand All @@ -23,6 +24,11 @@ def __init__(self, event: GoogleCalendarEventType):
self.end_time_utc = self._end_time.astimezone(datetime.timezone.utc)


class GoogleCalendarHTTPError(Exception):
def __init__(self, http_error) -> None:
self.error = http_error


class GoogleCalendarAPIClient:
MAX_NUMBER_OF_CALENDAR_EVENTS_TO_FETCH = 250
"""
Expand Down Expand Up @@ -68,17 +74,22 @@ def fetch_out_of_office_events(self) -> typing.List[GoogleCalendarEvent]:
now + datetime.timedelta(days=constants.DAYS_IN_FUTURE_TO_CONSIDER_OUT_OF_OFFICE_EVENTS)
)

events_result = (
self.service.events()
.list(
calendarId=self.CALENDAR_ID,
timeMin=time_min,
timeMax=time_max,
maxResults=self.MAX_NUMBER_OF_CALENDAR_EVENTS_TO_FETCH,
singleEvents=True,
orderBy="startTime",
eventTypes="outOfOffice",
try:
events_result = (
self.service.events()
.list(
calendarId=self.CALENDAR_ID,
timeMin=time_min,
timeMax=time_max,
maxResults=self.MAX_NUMBER_OF_CALENDAR_EVENTS_TO_FETCH,
singleEvents=True,
orderBy="startTime",
eventTypes="outOfOffice",
)
.execute()
)
.execute()
)
except HttpError as e:
logger.error(f"GoogleCalendarAPIClient - Error fetching out of office events: {e}")
raise GoogleCalendarHTTPError(e)

return [GoogleCalendarEvent(event) for event in events_result.get("items", [])]
10 changes: 8 additions & 2 deletions engine/apps/google/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from celery.utils.log import get_task_logger

from apps.google import constants
from apps.google.client import GoogleCalendarAPIClient
from apps.google.client import GoogleCalendarAPIClient, GoogleCalendarHTTPError
from apps.google.models import GoogleOAuth2User
from apps.schedules.models import OnCallSchedule, ShiftSwapRequest
from common.custom_celery_tasks import shared_dedicated_queue_retry_task
Expand Down Expand Up @@ -31,7 +31,13 @@ def sync_out_of_office_calendar_events_for_user(google_oauth2_user_pk: int) -> N
if oncall_schedules_to_consider_for_shift_swaps:
users_schedules = users_schedules.filter(public_primary_key__in=oncall_schedules_to_consider_for_shift_swaps)

for out_of_office_event in google_api_client.fetch_out_of_office_events():
try:
out_of_office_events = google_api_client.fetch_out_of_office_events()
except GoogleCalendarHTTPError:
logger.info(f"Failed to fetch out of office events for user {user_id}")
return

for out_of_office_event in out_of_office_events:
raw_event = out_of_office_event.raw_event

event_title = raw_event["summary"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import pytest
from django.utils import timezone
from googleapiclient.errors import HttpError

from apps.google import constants, tasks
from apps.schedules.models import CustomOnCallShift, OnCallScheduleWeb, ShiftSwapRequest
Expand Down Expand Up @@ -140,6 +141,28 @@ def _test_setup(out_of_office_events):
return _test_setup


class MockResponse:
def __init__(self, reason=None, status=200) -> None:
self.reason = reason or ""
self.status = status


@patch("apps.google.client.build")
@pytest.mark.django_db
def test_sync_out_of_office_calendar_events_for_user_httperror(mock_google_api_client_build, test_setup):
mock_response = MockResponse(reason="forbidden", status=403)
mock_google_api_client_build.return_value.events.return_value.list.return_value.execute.side_effect = HttpError(
resp=mock_response, content=b"error"
)

google_oauth2_user, schedule = test_setup([])
user = google_oauth2_user.user

tasks.sync_out_of_office_calendar_events_for_user(google_oauth2_user.pk)

assert ShiftSwapRequest.objects.filter(beneficiary=user, schedule=schedule).count() == 0


@patch("apps.google.client.build")
@pytest.mark.django_db
def test_sync_out_of_office_calendar_events_for_user_no_ooo_events(mock_google_api_client_build, test_setup):
Expand Down
12 changes: 1 addition & 11 deletions engine/apps/public_api/serializers/incidents.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from django.db.models import Prefetch
from rest_framework import serializers

from apps.alerts.models import AlertGroup
from apps.telegram.models.message import TelegramMessage
from common.api_helpers.custom_fields import UserIdField
from common.api_helpers.mixins import EagerLoadingMixin

Expand All @@ -19,14 +17,6 @@ class IncidentSerializer(EagerLoadingMixin, serializers.ModelSerializer):
resolved_by = UserIdField(read_only=True, source="resolved_by_user")

SELECT_RELATED = ["channel", "channel_filter", "slack_message", "channel__organization"]
PREFETCH_RELATED = [
"alerts",
Prefetch(
"telegram_messages",
TelegramMessage.objects.filter(chat_id__startswith="-", message_type=TelegramMessage.ALERT_GROUP_MESSAGE),
to_attr="prefetched_telegram_messages",
),
]

class Meta:
model = AlertGroup
Expand All @@ -50,7 +40,7 @@ def get_title(self, obj):
return obj.web_title_cache

def get_alerts_count(self, obj):
return len(obj.alerts.all())
return obj.alerts.count()

def get_state(self, obj):
return obj.state
Expand Down
4 changes: 4 additions & 0 deletions engine/apps/slack/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ class SlackAPIMessageNotFoundError(SlackAPIError):
errors = ("message_not_found",)


class SlackAPICantUpdateMessageError(SlackAPIError):
errors = ("cant_update_message",)


class SlackAPIUserNotFoundError(SlackAPIError):
errors = ("user_not_found",)

Expand Down
2 changes: 1 addition & 1 deletion engine/apps/slack/models/slack_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def permalink(self) -> typing.Optional[str]:

@property
def deep_link(self) -> str:
return f"slack://channel?team={self.slack_team_identity.slack_id}&id={self.channel_id}&message={self.slack_id}"
return f"https://slack.com/app_redirect?channel={self.channel_id}&team={self.slack_team_identity.slack_id}&message={self.slack_id}"

def send_slack_notification(self, user, alert_group, notification_policy):
from apps.base.models import UserNotificationPolicyLogRecord
Expand Down
2 changes: 2 additions & 0 deletions engine/apps/slack/scenarios/distribute_alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from apps.api.permissions import RBACPermission
from apps.slack.constants import CACHE_UPDATE_INCIDENT_SLACK_MESSAGE_LIFETIME
from apps.slack.errors import (
SlackAPICantUpdateMessageError,
SlackAPIChannelArchivedError,
SlackAPIChannelInactiveError,
SlackAPIChannelNotFoundError,
Expand Down Expand Up @@ -947,6 +948,7 @@ def update_log_message(self, alert_group: AlertGroup) -> None:
SlackAPIChannelArchivedError,
SlackAPIChannelInactiveError,
SlackAPIInvalidAuthError,
SlackAPICantUpdateMessageError,
):
pass
else:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from unittest.mock import patch

import pytest
from django.utils import timezone

from apps.alerts.models import AlertGroup
from apps.slack.errors import SlackAPIRestrictedActionError
from apps.slack.errors import SlackAPICantUpdateMessageError, SlackAPIRestrictedActionError
from apps.slack.models import SlackMessage
from apps.slack.scenarios.distribute_alerts import AlertShootingStep
from apps.slack.scenarios.distribute_alerts import AlertShootingStep, UpdateLogReportMessageStep
from apps.slack.scenarios.scenario_step import ScenarioStep
from apps.slack.tests.conftest import build_slack_response

Expand Down Expand Up @@ -64,3 +65,37 @@ def test_alert_shooting_no_channel_filter(

mock_post_alert_group_to_slack.assert_called_once()
assert mock_post_alert_group_to_slack.call_args[1]["channel_id"] == "DEFAULT_CHANNEL_ID"


@pytest.mark.django_db
def test_update_log_report_cant_update(
make_slack_team_identity,
make_organization,
make_alert_receive_channel,
make_alert_group,
make_alert,
make_slack_message,
):
slack_team_identity = make_slack_team_identity()
organization = make_organization(
slack_team_identity=slack_team_identity, general_log_channel_id="DEFAULT_CHANNEL_ID"
)
alert_receive_channel = make_alert_receive_channel(organization)

alert_group = make_alert_group(alert_receive_channel, channel_filter=None)
# alert = make_alert(alert_group, raw_request_data={})
log_message = make_slack_message(
alert_group=alert_group,
channel_id="RANDOM_CHANNEL_ID",
slack_id="RANDOM_MESSAGE_ID",
last_updated=timezone.now() - timezone.timedelta(minutes=5),
)
alert_group.slack_log_message = log_message

step = UpdateLogReportMessageStep(slack_team_identity, organization)
with patch.object(step._slack_client, "api_call") as mock_slack_api_call:
mock_slack_api_call.side_effect = SlackAPICantUpdateMessageError(
response=build_slack_response({"error": "cant_update_message"})
)
# not raising error, will not retry
step.update_log_message(alert_group)
2 changes: 2 additions & 0 deletions engine/apps/slack/tests/test_slack_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from apps.slack.client import SlackClient, server_error_retry_handler
from apps.slack.errors import (
SlackAPICannotDMBotError,
SlackAPICantUpdateMessageError,
SlackAPIChannelArchivedError,
SlackAPIChannelInactiveError,
SlackAPIChannelNotFoundError,
Expand Down Expand Up @@ -116,6 +117,7 @@ def test_slack_client_generic_error(mock_request, monkeypatch, make_organization
[
("account_inactive", SlackAPITokenError),
("cannot_dm_bot", SlackAPICannotDMBotError),
("cant_update_message", SlackAPICantUpdateMessageError),
("channel_not_found", SlackAPIChannelNotFoundError),
("fatal_error", SlackAPIServerError),
("fetch_members_failed", SlackAPIFetchMembersFailedError),
Expand Down
5 changes: 4 additions & 1 deletion engine/apps/slack/tests/test_slack_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,8 @@ def test_slack_message_deep_link(
slack_channel = make_slack_channel(slack_team_identity)
slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id)

expected = f"slack://channel?team={slack_team_identity.slack_id}&id={slack_channel.slack_id}&message={slack_message.slack_id}"
expected = (
f"https://slack.com/app_redirect?channel={slack_channel.slack_id}"
f"&team={slack_team_identity.slack_id}&message={slack_message.slack_id}"
)
assert slack_message.deep_link == expected
1 change: 1 addition & 0 deletions engine/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ module = [
"fcm_django.*",
"firebase_admin.*",
"googleapiclient.discovery.*",
"googleapiclient.errors.*",
"google.oauth2.credentials.*",
"httpretty.*",
"humanize.*",
Expand Down
2 changes: 1 addition & 1 deletion grafana-plugin/src/models/filters/filters.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const getApiPathByPage = (page: string) => {
);
};

export const convertFiltersToBackendFormat = (filters: FiltersValues, filterOptions: FilterOption[]) => {
export const convertFiltersToBackendFormat = (filters: FiltersValues = {}, filterOptions: FilterOption[] = []) => {
const newFilters = { ...filters };
filterOptions.forEach((filterOption) => {
if (filterOption.type === 'daterange' && newFilters[filterOption.name]) {
Expand Down
Loading