From 77a8d3db21b6a91dbdce58d19da4b1002ae85d3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:10:42 +0000 Subject: [PATCH 1/7] Bump fast-loops from 1.1.3 to 1.1.4 in /grafana-plugin (#4650) Bumps [fast-loops](https://github.com/robinweser/fast-loops) from 1.1.3 to 1.1.4.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=fast-loops&package-manager=npm_and_yarn&previous-version=1.1.3&new-version=1.1.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/grafana/oncall/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- grafana-plugin/yarn.lock | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/grafana-plugin/yarn.lock b/grafana-plugin/yarn.lock index 24a21c2c..f28148c9 100644 --- a/grafana-plugin/yarn.lock +++ b/grafana-plugin/yarn.lock @@ -7085,9 +7085,9 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-loops@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-loops/-/fast-loops-1.1.3.tgz#ce96adb86d07e7bf9b4822ab9c6fac9964981f75" - integrity sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g== + version "1.1.4" + resolved "https://registry.yarnpkg.com/fast-loops/-/fast-loops-1.1.4.tgz#61bc77d518c0af5073a638c6d9d5c7683f069ce2" + integrity sha512-8dbd3XWoKCTms18ize6JmQF1SFnnfj5s0B7rRry22EofgMu7B6LKHVh+XfFqFGsqnbH54xgeO83PzpKI+ODhlg== fast-safe-stringify@^2.0.7: version "2.1.1" @@ -13469,7 +13469,16 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13587,7 +13596,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -13608,6 +13617,13 @@ strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -14948,8 +14964,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: - name wrap-ansi-cjs +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -14967,6 +14982,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 7da58c8eaaa1ad9e0792b9464d7204b77d73479a Mon Sep 17 00:00:00 2001 From: Dominik Broj Date: Thu, 11 Jul 2024 12:28:46 +0200 Subject: [PATCH 2/7] run `mage buildAll || true` when building the plugin (#4658) # What this PR does `oncall-private` is using [actions/build-sign-and-package-plugin/action.yml](https://github.com/grafana/oncall/pull/4658/files#diff-6200ac63c387869ca55bc34d1b343a96c667daef428602f910d696836cb09f84) from the dev branch of this repo when doing the deployment. In order to deploy new OnCall initialization which has Go backend component, we need this action to install Go, Mage and do the `mage buildAll`. For other branches where there are no Go files yet, it would just swallow the error thanks to the `|| true` part ## 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. --- .github/actions/build-sign-and-package-plugin/action.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/actions/build-sign-and-package-plugin/action.yml b/.github/actions/build-sign-and-package-plugin/action.yml index 7ce4e42a..731d47b1 100644 --- a/.github/actions/build-sign-and-package-plugin/action.yml +++ b/.github/actions/build-sign-and-package-plugin/action.yml @@ -27,6 +27,13 @@ runs: # yamllint disable rule:line-length run: | echo filename="grafana-oncall${{ inputs.is_enterprise == 'true' && '-ee' || '' }}-app-${{ inputs.plugin_version_number }}.zip" >> $GITHUB_OUTPUT + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: "1.21.5" + - name: Install Mage + shell: bash + run: go install github.com/magefile/mage@v1.15.0 - name: Build, sign, and package plugin shell: bash working-directory: ${{ inputs.working_directory }} @@ -35,6 +42,7 @@ runs: run: | jq --arg v "${{ inputs.plugin_version_number }}" '.version=$v' package.json > package.new && mv package.new package.json && jq '.version' package.json; yarn build + mage buildAll || true yarn sign if [ ! -f dist/MANIFEST.txt ]; then echo "Sign failed, MANIFEST.txt not created, aborting." && exit 1; fi mv dist grafana-oncall-app From d7d4a92c2817fe10d043eb43f24bb5a55b2a07b0 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Thu, 11 Jul 2024 14:50:18 +0300 Subject: [PATCH 3/7] Enable schedule view e2e test (#4424) # What this PR does Enable schedule view e2e test ## Which issue(s) this PR closes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [ ] 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. --------- Co-authored-by: Joey Orlando Co-authored-by: Dominik Broj --- .../e2e-tests/schedules/scheduleView.test.ts | 31 ++++++++----- .../TimelineMarks/TimelineMarks.tsx | 3 +- .../src/pages/schedule/Schedule.tsx | 44 ++++++++++--------- 3 files changed, 46 insertions(+), 32 deletions(-) diff --git a/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts b/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts index 0f27f47a..a76025b6 100644 --- a/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts +++ b/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts @@ -1,31 +1,42 @@ +import semver from 'semver'; + import { scheduleViewToDaysInOneRow } from 'models/schedule/schedule.helpers'; import { ScheduleView } from 'models/schedule/schedule.types'; import { HTML_ID } from 'utils/DOM'; -import { expect, test } from '../fixtures'; +import { expect, Page, test } from '../fixtures'; import { generateRandomValue } from '../utils/forms'; import { createOnCallSchedule } from '../utils/schedule'; -test.skip('schedule view (week/2 weeks/month) toggler works', async ({ adminRolePage }) => { +const getNumberOfWeekdaysInFinalSchedule = async (page: Page) => + await page.locator(`#${HTML_ID.SCHEDULE_FINAL}`).getByTestId('schedule-weekday').count(); +const getScheduleViewRadioButtonLocator = (page: Page, view: ScheduleView) => + page + .getByTestId('schedule-view-picker') + [semver.lt(process.env.CURRENT_GRAFANA_VERSION, '10.2.0') ? 'getByText' : 'getByLabel'](view, { exact: true }); + +test('schedule view (week/2 weeks/month) toggler works', async ({ adminRolePage }) => { const { page, userName } = adminRolePage; const onCallScheduleName = generateRandomValue(); await createOnCallSchedule(page, onCallScheduleName, userName); // ScheduleView.OneWeek is selected by default - expect(await page.getByLabel(ScheduleView.OneWeek, { exact: true }).isChecked()).toBe(true); + expect(await getScheduleViewRadioButtonLocator(page, ScheduleView.OneWeek).isChecked()).toBe(true); - expect(await page.locator(`#${HTML_ID.SCHEDULE_FINAL} .TEST_weekday`).count()).toStrictEqual( + expect(await getNumberOfWeekdaysInFinalSchedule(page)).toStrictEqual( scheduleViewToDaysInOneRow[ScheduleView.OneWeek] ); - await page.getByLabel(ScheduleView.TwoWeeks, { exact: true }).click(); - expect(await page.getByLabel(ScheduleView.TwoWeeks, { exact: true }).isChecked()).toBe(true); - expect(await page.locator(`#${HTML_ID.SCHEDULE_FINAL} .TEST_weekday`).count()).toStrictEqual( + await getScheduleViewRadioButtonLocator(page, ScheduleView.TwoWeeks).click(); + await page.waitForTimeout(1000); + expect(await getScheduleViewRadioButtonLocator(page, ScheduleView.TwoWeeks).isChecked()).toBe(true); + expect(await getNumberOfWeekdaysInFinalSchedule(page)).toStrictEqual( scheduleViewToDaysInOneRow[ScheduleView.TwoWeeks] ); - await page.getByLabel(ScheduleView.OneMonth, { exact: true }).click(); - expect(await page.getByLabel(ScheduleView.OneMonth, { exact: true }).isChecked()).toBe(true); - expect(await page.locator(`#${HTML_ID.SCHEDULE_FINAL} .TEST_weekday`).count()).toBeGreaterThanOrEqual(28); + await getScheduleViewRadioButtonLocator(page, ScheduleView.OneMonth).click(); + await page.waitForTimeout(1000); + expect(await getScheduleViewRadioButtonLocator(page, ScheduleView.OneMonth).isChecked()).toBe(true); + expect(await getNumberOfWeekdaysInFinalSchedule(page)).toBeGreaterThanOrEqual(28); }); diff --git a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx index 9685b667..c14c3cc4 100644 --- a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx +++ b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx @@ -84,7 +84,8 @@ export const TimelineMarks: FC = observer((props) => { return (
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