diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx
index 6264504e..997cbb30 100644
--- a/grafana-plugin/src/pages/schedule/Schedule.tsx
+++ b/grafana-plugin/src/pages/schedule/Schedule.tsx
@@ -356,28 +356,30 @@ class _SchedulePage extends React.Component
- {
- scheduleStore.setScheduleView(value);
- if (value === ScheduleView.OneMonth) {
- timezoneStore.setCalendarStartDate(
- getCalendarStartDate(
- timezoneStore.calendarStartDate.endOf('isoWeek').startOf('month'),
- value,
- timezoneStore.selectedTimezoneOffset
- )
- );
- }
+
+ {
+ scheduleStore.setScheduleView(value);
+ if (value === ScheduleView.OneMonth) {
+ timezoneStore.setCalendarStartDate(
+ getCalendarStartDate(
+ timezoneStore.calendarStartDate.endOf('isoWeek').startOf('month'),
+ value,
+ timezoneStore.selectedTimezoneOffset
+ )
+ );
+ }
- scheduleStore.refreshEvents(scheduleId);
- }}
- />
+ scheduleStore.refreshEvents(scheduleId);
+ }}
+ />
+
this.setState({ filters: value })}
From 997167fcbe20977ceb6c037ff0bcafd3fbc7663b Mon Sep 17 00:00:00 2001
From: Vadim Stepanov
Date: Thu, 11 Jul 2024 13:21:12 +0100
Subject: [PATCH 4/7] Fix changing recurrence for weekday masked shifts (#4660)
# What this PR does
Fixes a bug when changing recurrence period for shifts with "Mask by
weekdays" setting enabled breaks the schedule.
https://github.com/grafana/oncall/assets/20116910/396359c5-4ba6-47d7-b003-fea9db709bea
## Which issue(s) this PR closes
Related to https://github.com/grafana/support-escalations/issues/11374
## Checklist
- [ ] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
show up in the autogenerated release notes.
---
grafana-plugin/src/containers/RotationForm/RotationForm.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx
index b5b63239..27fdd8ba 100644
--- a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx
+++ b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx
@@ -337,7 +337,7 @@ export const RotationForm = observer((props: RotationFormProps) => {
setShiftPeriodDefaultValue(undefined);
setRecurrenceNum(value);
- if (!isLimitShiftEnabled) {
+ if (!isLimitShiftEnabled && !isMaskedByWeekdays) {
setShiftEnd(
dayJSAddWithDSTFixed({
baseDate: rotationStart,
From e583d5fc52f8a696bb1e2d390e837409934b36a2 Mon Sep 17 00:00:00 2001
From: Matias Bordese
Date: Thu, 11 Jul 2024 09:51:01 -0300
Subject: [PATCH 5/7] Handle inbound email integration alert getting multiple
recipients (#4655)
Related to https://github.com/grafana/oncall-private/issues/2683
(when using mailgun backend, you can get multiple recipients, keep the
first one matching the domain; other backends seem to just return the
first one)
---
engine/apps/email/inbound.py | 26 ++++---
engine/apps/email/tests/test_inbound_email.py | 77 +++++++++++++++++--
2 files changed, 88 insertions(+), 15 deletions(-)
diff --git a/engine/apps/email/inbound.py b/engine/apps/email/inbound.py
index 76eec2aa..1780f00c 100644
--- a/engine/apps/email/inbound.py
+++ b/engine/apps/email/inbound.py
@@ -86,15 +86,18 @@ class InboundEmailWebhookView(AlertChannelDefiningMixin, APIView):
# First try envelope_recipient field.
# According to AnymailInboundMessage it's provided not by all ESPs.
if message.envelope_recipient:
- try:
- token, domain = message.envelope_recipient.split("@")
- except ValueError:
- logger.error(
- f"get_integration_token_from_request: envelope_recipient field has unexpected format: {message.envelope_recipient}"
- )
- return None
- if domain == live_settings.INBOUND_EMAIL_DOMAIN:
- return token
+ recipients = message.envelope_recipient.split(",")
+ for recipient in recipients:
+ # if there is more than one recipient, the first matching the expected domain will be used
+ try:
+ token, domain = recipient.strip().split("@")
+ except ValueError:
+ logger.error(
+ f"get_integration_token_from_request: envelope_recipient field has unexpected format: {message.envelope_recipient}"
+ )
+ continue
+ if domain == live_settings.INBOUND_EMAIL_DOMAIN:
+ return token
else:
logger.info("get_integration_token_from_request: message.envelope_recipient is not present")
"""
@@ -152,7 +155,10 @@ class InboundEmailWebhookView(AlertChannelDefiningMixin, APIView):
def get_sender_from_email_message(self, email: AnymailInboundMessage) -> str:
try:
- sender = email.from_email.addr_spec
+ if isinstance(email.from_email, list):
+ sender = email.from_email[0].addr_spec
+ else:
+ sender = email.from_email.addr_spec
except AnymailInvalidAddress as e:
# wasn't able to parse email address from message, return raw value from "From" header
logger.warning(
diff --git a/engine/apps/email/tests/test_inbound_email.py b/engine/apps/email/tests/test_inbound_email.py
index a47b19f9..bf6ae5ae 100644
--- a/engine/apps/email/tests/test_inbound_email.py
+++ b/engine/apps/email/tests/test_inbound_email.py
@@ -1,4 +1,5 @@
import json
+from textwrap import dedent
import pytest
from anymail.inbound import AnymailInboundMessage
@@ -9,8 +10,19 @@ from rest_framework.test import APIClient
from apps.email.inbound import InboundEmailWebhookView
+@pytest.mark.parametrize(
+ "recipients,expected",
+ [
+ ("{token}@example.com", status.HTTP_200_OK),
+ ("{token}@example.com, another@example.com", status.HTTP_200_OK),
+ ("{token}@example.com, another@example.com", status.HTTP_200_OK),
+ ("{token}@other.com, {token}@example.com", status.HTTP_400_BAD_REQUEST),
+ ],
+)
@pytest.mark.django_db
-def test_amazon_ses_provider_load(settings, make_organization_and_user_with_token, make_alert_receive_channel):
+def test_amazon_ses_provider_load(
+ settings, make_organization_and_user_with_token, make_alert_receive_channel, recipients, expected
+):
settings.INBOUND_EMAIL_ESP = "amazon_ses"
settings.INBOUND_EMAIL_DOMAIN = "example.com"
@@ -19,10 +31,10 @@ def test_amazon_ses_provider_load(settings, make_organization_and_user_with_toke
organization, _, token = make_organization_and_user_with_token()
_ = make_alert_receive_channel(organization, token=dummy_channel_token)
- recipient = f"{dummy_channel_token}@example.com"
+ recipients = recipients.format(token=dummy_channel_token)
mime = f"""From: sender@example.com
Subject: Dummy email message
- To: {recipient}
+ To: {recipients}
Content-Type: text/plain
Hello!
@@ -30,7 +42,7 @@ def test_amazon_ses_provider_load(settings, make_organization_and_user_with_toke
message = {
"notificationType": "Received",
- "receipt": {"action": {"type": "SNS"}, "recipients": [recipient]},
+ "receipt": {"action": {"type": "SNS"}, "recipients": recipients.split(",")},
"content": mime,
}
@@ -54,7 +66,62 @@ def test_amazon_ses_provider_load(settings, make_organization_and_user_with_toke
HTTP_X_AMZ_SNS_MESSAGE_ID=dummy_sns_message_id,
)
- assert response.status_code == status.HTTP_200_OK
+ assert response.status_code == expected
+
+
+@pytest.mark.parametrize(
+ "recipients,expected",
+ [
+ ("{token}@example.com", status.HTTP_200_OK),
+ ("{token}@example.com, another@example.com", status.HTTP_200_OK),
+ ("{token}@example.com, another@example.com", status.HTTP_200_OK),
+ ("{token}@other.com, {token}@example.com", status.HTTP_200_OK),
+ ("{token}@other.com, {token}@another.com", status.HTTP_400_BAD_REQUEST),
+ ],
+)
+@pytest.mark.django_db
+def test_mailgun_provider_load(
+ settings, make_organization_and_user_with_token, make_alert_receive_channel, recipients, expected
+):
+ settings.INBOUND_EMAIL_ESP = "mailgun"
+ settings.INBOUND_EMAIL_DOMAIN = "example.com"
+ settings.INBOUND_EMAIL_WEBHOOK_SECRET = "secret"
+
+ dummy_channel_token = "dummy-channel-token"
+
+ organization, _, token = make_organization_and_user_with_token()
+ _ = make_alert_receive_channel(organization, token=dummy_channel_token)
+
+ recipients = recipients.format(token=dummy_channel_token)
+ raw_event = {
+ "token": "06c96bafc3f42a66b9edd546347a2fe18dc23461fe80dc52f0",
+ "timestamp": "1461261330",
+ "signature": "dbb05e62be402448b36ffb81f6abfb888273c95617aa077b4da355b25bab3670",
+ "recipient": "{recipients}".format(recipients=recipients),
+ "sender": "envelope-from@example.org",
+ "body-mime": dedent(
+ """\
+ From: sender@example.com
+ Subject: Dummy email message
+ To: {recipients}
+ Content-Type: text/plain
+
+ Hello!
+ --94eb2c05e174adb140055b6339c5--
+ """.format(
+ recipients=recipients
+ )
+ ),
+ }
+
+ client = APIClient()
+ response = client.post(
+ reverse("integrations:inbound_email_webhook"),
+ data=raw_event,
+ HTTP_AUTHORIZATION=token,
+ )
+
+ assert response.status_code == expected
@pytest.mark.parametrize(
From b421296e13ccf0b2b6b1a0e46fb3263e824024d5 Mon Sep 17 00:00:00 2001
From: Vadim Stepanov
Date: Thu, 11 Jul 2024 17:28:21 +0100
Subject: [PATCH 6/7] Fix `start_sync_org_with_chatops_proxy` crontab (#4661)
Related to
https://raintank-corp.slack.com/archives/C04JCU51NF8/p1720695995646029
---
engine/settings/base.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/engine/settings/base.py b/engine/settings/base.py
index d185d8bb..6638d16b 100644
--- a/engine/settings/base.py
+++ b/engine/settings/base.py
@@ -594,7 +594,7 @@ START_SYNC_ORG_WITH_CHATOPS_PROXY_ENABLED = getenv_boolean("START_SYNC_ORG_WITH_
if FEATURE_MULTIREGION_ENABLED and START_SYNC_ORG_WITH_CHATOPS_PROXY_ENABLED:
CELERY_BEAT_SCHEDULE["start_sync_org_with_chatops_proxy"] = {
"task": "apps.chatops_proxy.tasks.start_sync_org_with_chatops_proxy",
- "schedule": crontab(hour="*/24"), # Every 24 hours, feel free to adjust
+ "schedule": crontab(minute=0, hour=12), # Execute every day at noon
"args": (),
}
From 324b0c42868721712e7712288812006fc0e66556 Mon Sep 17 00:00:00 2001
From: Vadim Stepanov
Date: Thu, 11 Jul 2024 17:47:52 +0100
Subject: [PATCH 7/7] Make docs clearer for "Notify users one by one" policy
(#4662)
# What this PR does
Clarifies how the `Notify users one by one (round robin)` escalation
policy works.
## Which issue(s) this PR closes
Related to https://github.com/grafana/support-escalations/issues/11449
## Checklist
- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
show up in the autogenerated release notes.
---
.../configure/escalation-chains-and-routes/index.md | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/docs/sources/configure/escalation-chains-and-routes/index.md b/docs/sources/configure/escalation-chains-and-routes/index.md
index 1b497b3a..8f44e8fb 100644
--- a/docs/sources/configure/escalation-chains-and-routes/index.md
+++ b/docs/sources/configure/escalation-chains-and-routes/index.md
@@ -88,8 +88,11 @@ via the method configured in their user profile.
* `Notify Slack User Group` - send a notification to each member of a slack user group. These users will be notified
via the method configured in their user profile.
* `Trigger outgoing webhook` - trigger an [outgoing webhook].
-* `Notify users one by one (round robin)` - each notification will be sent to a group of
-users one by one, in sequential order in [round robin fashion](https://en.wikipedia.org/wiki/Round-robin_item_allocation).
+* `Notify users one by one (round robin)` - notify users sequentially, cycling through users for **different alert groups**.
+Example: if users A, B, and C are in the list, the first alert group notifies A, the second alert group notifies B, and
+the third alert group notifies C. Note: users are sorted alphabetically by their username.
+To notify multiple users **within the same alert group** until someone acknowledges, instead use `Notify users` policies with
+`Wait` policies between them in the escalation chain.
* `Continue escalation if current time is in range` - continue escalation only if current
time is in specified range. It will wait for the specfied time to continue escalation.
Useful when you want to get escalation only during working hours