Skip to content

Commit

Permalink
Merge pull request #5106 from grafana/dev
Browse files Browse the repository at this point in the history
v1.9.31
  • Loading branch information
vadimkerr authored Oct 1, 2024
2 parents e278a76 + e9d94eb commit 75e8374
Show file tree
Hide file tree
Showing 41 changed files with 394 additions and 345 deletions.
2 changes: 2 additions & 0 deletions .markdownlintignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ venv
.idea
.DS_Store
.env

CHANGELOG.md
2 changes: 1 addition & 1 deletion dev/helm-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ grafana:
- name: DATABASE_PASSWORD
value: oncallpassword
env:
GF_FEATURE_TOGGLES_ENABLE: topnav,externalServiceAccounts
GF_FEATURE_TOGGLES_ENABLE: externalServiceAccounts
GF_SECURITY_ADMIN_PASSWORD: oncall
GF_SECURITY_ADMIN_USER: oncall
GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: grafana-oncall-app
Expand Down
12 changes: 6 additions & 6 deletions docs/sources/manage/notify/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,21 @@ To configure supported messaging apps, refer to the following topics:

{{< section >}}

## Configure user notification policies
## Configure user notification rules

Notification policies are a configurable set of notification steps that determine how you're notified of alert in OnCall. Users with the Admin or Editor role are
Notification rules are a configurable set of notification steps that determine how you're notified of alert in OnCall. Users with the Admin or Editor role are
able to receive notifications.
Users can verify phone numbers and email addresses in the **Users** tab of Grafana OnCall.

- **Default Notifications** dictate how a user is notified for most escalation thresholds.
- **Default notification rules** dictate how a user is notified for most escalation thresholds.

- **Important Notifications** are labeled in escalation chains. If an escalation event is marked as an important notification,
it will bypass **Default Notification** settings and notify the user by the method specified.
- **Important notification rules** are labeled in escalation chains. If an escalation event is marked as an important notification,
it will bypass **Default notification rules** settings and notify the user by the method specified.

> **NOTE**: You cannot add users or manage permissions in Grafana OnCall. User settings are found on the
> organizational level of your Grafana instance in **Configuration > Users**.
To configure a users notification policy:
To configure a users notification rules:

1. Navigate to the **Users** tab of Grafana OnCall and search for or select a user.

Expand Down
1 change: 1 addition & 0 deletions engine/apps/alerts/tests/test_paging.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def test_user_is_oncall(make_organization, make_user_for_organization, make_sche
)
on_call_shift.add_rolling_users([[oncall_user]])
schedule.refresh_ical_file()
schedule.refresh_ical_final_schedule()

assert user_is_oncall(not_oncall_user) is False
assert user_is_oncall(oncall_user) is True
Expand Down
38 changes: 38 additions & 0 deletions engine/apps/api/tests/test_alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -2344,3 +2344,41 @@ def test_alert_group_external_urls(
"url": "https://some-url",
}
assert response.json()["external_urls"] == [expected]


@pytest.mark.django_db
def test_filter_default_started_at(
make_organization_and_user_with_plugin_token,
make_alert_receive_channel,
make_channel_filter,
make_alert_group,
make_alert,
make_user_auth_headers,
):
organization, user, token = make_organization_and_user_with_plugin_token()
alert_receive_channel = make_alert_receive_channel(organization)
channel_filter = make_channel_filter(alert_receive_channel, is_default=True)

old_alert_group = make_alert_group(alert_receive_channel, channel_filter=channel_filter)
old_alert_group.started_at = timezone.now() - timezone.timedelta(days=31)
old_alert_group.save(update_fields=["started_at"])
make_alert(alert_group=old_alert_group, raw_request_data=alert_raw_request_data)

new_alert_group = make_alert_group(alert_receive_channel, channel_filter=channel_filter)
make_alert(alert_group=new_alert_group, raw_request_data=alert_raw_request_data)

client = APIClient()

# by default, no alert groups older than 30 days should be listed
response = client.get(reverse("api-internal:alertgroup-list"), **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()["results"]) == 1
assert response.json()["results"][0]["pk"] == new_alert_group.public_primary_key

# but it should still be possible to retrieve an old alert group by its ID
response = client.get(
reverse("api-internal:alertgroup-detail", kwargs={"pk": old_alert_group.public_primary_key}),
**make_user_auth_headers(user, token),
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["pk"] == old_alert_group.public_primary_key
4 changes: 4 additions & 0 deletions engine/apps/api/tests/test_schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ def test_get_list_schedules_by_mine(
)
override.add_rolling_users([[user]])
web_schedule.refresh_ical_file()
web_schedule.refresh_ical_final_schedule()

url = reverse("api-internal:schedule-list") + query_param
response = client.get(url, format="json", **make_user_auth_headers(user, token))
Expand Down Expand Up @@ -1497,6 +1498,7 @@ def test_next_shifts_per_user(
override.add_rolling_users([[user_c]])

# final schedule: 7-12: B, 15-16: A, 16-17: B, 17-18: C (override), 18-20: C
schedule.refresh_ical_final_schedule()

url = reverse("api-internal:schedule-next-shifts-per-user", kwargs={"pk": schedule.public_primary_key})
response = client.get(url, format="json", **make_user_auth_headers(admin, token))
Expand Down Expand Up @@ -1569,6 +1571,7 @@ def test_next_shifts_per_user_ical_schedule_using_emails(
schedule_class=OnCallScheduleICal,
cached_ical_file_primary=cached_ical_primary_schedule,
)
schedule.refresh_ical_final_schedule()

url = reverse("api-internal:schedule-next-shifts-per-user", kwargs={"pk": schedule.public_primary_key})
response = client.get(url, format="json", **make_user_auth_headers(admin, token))
Expand Down Expand Up @@ -1628,6 +1631,7 @@ def test_related_users(
)
override.add_rolling_users([[user_c]])
schedule.refresh_ical_file()
schedule.refresh_ical_final_schedule()

url = reverse("api-internal:schedule-related-users", kwargs={"pk": schedule.public_primary_key})
response = client.get(url, format="json", **make_user_auth_headers(admin, token))
Expand Down
3 changes: 1 addition & 2 deletions engine/apps/api/views/alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,7 @@ def get_queryset(self, ignore_filtering_by_available_teams=False):
alert_receive_channels_ids = list(alert_receive_channels_qs.values_list("id", flat=True))
queryset = AlertGroup.objects.filter(channel__in=alert_receive_channels_ids)

# This is a quick fix to speed up requests from mobile app by adding default `started_at` filter value
if not self.request.query_params.get("started_at"):
if self.action in ("list", "stats") and not self.request.query_params.get("started_at"):
queryset = queryset.filter(started_at__gte=timezone.now() - timezone.timedelta(days=30))

if self.action in ("list", "stats") and settings.ALERT_GROUPS_DISABLE_PREFER_ORDERING_INDEX:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ def test_notify_shift_swap_request_success(
on_call_shift.add_rolling_users([[user]])

schedule.refresh_ical_file()
schedule.refresh_ical_final_schedule()
schedule.refresh_from_db()

swap_start = now + timezone.timedelta(days=100)
Expand Down
22 changes: 3 additions & 19 deletions engine/apps/schedules/models/on_call_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from django.conf import settings
from django.core.validators import MinLengthValidator
from django.db import models
from django.db.models import Q
from django.db.utils import DatabaseError
from django.utils import timezone
from django.utils.functional import cached_property
Expand Down Expand Up @@ -158,10 +157,7 @@ def get_oncall_users(self, events_datetime=None):
def related_to_user(self, user):
username_regex = RE_ICAL_SEARCH_USERNAME.format(user.username)
return self.filter(
Q(cached_ical_file_primary__regex=username_regex)
| Q(cached_ical_file_primary__contains=user.email)
| Q(cached_ical_file_overrides__regex=username_regex)
| Q(cached_ical_file_overrides__contains=user.email),
cached_ical_final_schedule__regex=username_regex,
organization=user.organization,
)

Expand Down Expand Up @@ -344,10 +340,8 @@ def _drop_overrides_ical_file(self):
def related_users(self):
"""Return users referenced in the schedule."""
usernames = []
if self.cached_ical_file_primary:
usernames += RE_ICAL_FETCH_USERNAME.findall(self.cached_ical_file_primary)
if self.cached_ical_file_overrides:
usernames += RE_ICAL_FETCH_USERNAME.findall(self.cached_ical_file_overrides)
if self.cached_ical_final_schedule:
usernames += RE_ICAL_FETCH_USERNAME.findall(self.cached_ical_final_schedule)
return self.organization.users.filter(username__in=usernames)

def filter_events(
Expand Down Expand Up @@ -1128,16 +1122,6 @@ def _refresh_overrides_ical_file(self):
)
self.save(update_fields=["cached_ical_file_overrides", "prev_ical_file_overrides", "ical_file_error_overrides"])

def related_users(self):
"""Return users referenced in the schedule."""
# combine users based on usernames and users via email (allowed in iCal based schedules)
usernames = []
if self.cached_ical_file_primary:
usernames += RE_ICAL_FETCH_USERNAME.findall(self.cached_ical_file_primary)
if self.cached_ical_file_overrides:
usernames += RE_ICAL_FETCH_USERNAME.findall(self.cached_ical_file_overrides)
return self.organization.users.filter(Q(username__in=usernames) | Q(email__in=usernames))

# Insight logs
@property
def insight_logs_serialized(self):
Expand Down
41 changes: 39 additions & 2 deletions engine/apps/schedules/tests/test_on_call_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -1206,7 +1206,7 @@ def test_schedule_related_users(make_organization, make_user_for_organization, m
now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
start_date = now - timezone.timedelta(days=7)

user_a, _, _, user_d, user_e = (make_user_for_organization(organization, username=i) for i in "ABCDE")
user_a, user_b, _, user_d, user_e = (make_user_for_organization(organization, username=i) for i in "ABCDE")

shifts = (
# user, priority, start time (h), duration (hs)
Expand Down Expand Up @@ -1239,7 +1239,20 @@ def test_schedule_related_users(make_organization, make_user_for_organization, m
)
override.add_rolling_users([[user_e]])

# override: 22-23, a month ago / B (won't be considered a related user)
override_data = {
"start": start_date - timezone.timedelta(hours=22, days=30),
"rotation_start": start_date - timezone.timedelta(hours=22, days=30),
"duration": timezone.timedelta(hours=1),
"schedule": schedule,
}
override = make_on_call_shift(
organization=organization, shift_type=CustomOnCallShift.TYPE_OVERRIDE, **override_data
)
override.add_rolling_users([[user_b]])

schedule.refresh_ical_file()
schedule.refresh_ical_final_schedule()
schedule.refresh_from_db()

users = schedule.related_users()
Expand Down Expand Up @@ -1282,6 +1295,7 @@ def test_schedule_related_users_usernames(
on_call_shift.add_rolling_users([[user]])

schedule.refresh_ical_file()
schedule.refresh_ical_final_schedule()
schedule.refresh_from_db()

assert set(schedule.related_users()) == set(users)
Expand All @@ -1304,7 +1318,7 @@ def test_schedule_related_users_emails(make_organization, make_user_for_organiza
DTSTAMP:20230127T151619Z
UID:something
SUMMARY:[email protected]
RRULE:FREQ=WEEKLY;UNTIL=20221231T010101
RRULE:FREQ=WEEKLY
DTSTART;TZID=Europe/Madrid:20220309T130000
DTEND;TZID=Europe/Madrid:20220309T133000
SEQUENCE:4
Expand All @@ -1317,6 +1331,8 @@ def test_schedule_related_users_emails(make_organization, make_user_for_organiza
schedule_class=OnCallScheduleICal,
cached_ical_file_primary=cached_ical_primary_schedule,
)
schedule.refresh_ical_final_schedule()
schedule.refresh_from_db()

assert set(schedule.related_users()) == {user}

Expand Down Expand Up @@ -1604,6 +1620,7 @@ def test_user_related_schedules(
)
on_call_shift.add_rolling_users([[user]])
schedule1.refresh_ical_file()
schedule1.refresh_ical_final_schedule()

schedule2 = make_schedule(organization, schedule_class=OnCallScheduleWeb)
override_data = {
Expand All @@ -1617,10 +1634,27 @@ def test_user_related_schedules(
)
override.add_rolling_users([[admin]])
schedule2.refresh_ical_file()
schedule2.refresh_ical_final_schedule()

# schedule3
make_schedule(organization, schedule_class=OnCallScheduleWeb)

# schedule4
schedule4 = make_schedule(organization, schedule_class=OnCallScheduleWeb)
# user was part of the schedule some time ago (outside of the final schedule window)
override_data = {
"start": today - timezone.timedelta(days=21),
"rotation_start": today - timezone.timedelta(days=21),
"duration": timezone.timedelta(hours=1),
"schedule": schedule4,
}
override = make_on_call_shift(
organization=organization, shift_type=CustomOnCallShift.TYPE_OVERRIDE, **override_data
)
override.add_rolling_users([[admin]])
schedule4.refresh_ical_file()
schedule4.refresh_ical_final_schedule()

schedules = OnCallSchedule.objects.related_to_user(admin)
assert set(schedules) == {schedule1, schedule2}

Expand Down Expand Up @@ -1658,6 +1692,7 @@ def test_user_related_schedules_only_username(
)
on_call_shift.add_rolling_users([[user]])
schedule1.refresh_ical_file()
schedule1.refresh_ical_final_schedule()

schedule2 = make_schedule(organization, schedule_class=OnCallScheduleWeb)
override_data = {
Expand All @@ -1671,6 +1706,7 @@ def test_user_related_schedules_only_username(
)
override.add_rolling_users([[user]])
schedule2.refresh_ical_file()
schedule2.refresh_ical_final_schedule()

# schedule3
schedule3 = make_schedule(organization, schedule_class=OnCallScheduleWeb)
Expand All @@ -1685,6 +1721,7 @@ def test_user_related_schedules_only_username(
)
override.add_rolling_users([[other_user]])
schedule3.refresh_ical_file()
schedule3.refresh_ical_final_schedule()

schedules = OnCallSchedule.objects.related_to_user(user)
assert set(schedules) == {schedule1, schedule2}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def test_add_user_no_warning(manage_responders_setup, make_schedule, make_on_cal
)
on_call_shift.add_rolling_users([[user]])
schedule.refresh_ical_file()
schedule.refresh_ical_final_schedule()
# setup notification policy
make_user_notification_policy(
user=user,
Expand Down Expand Up @@ -199,6 +200,7 @@ def test_get_users_select(make_organization, make_user, make_schedule, make_on_c
)
on_call_shift.add_rolling_users([[oncall_user]])
schedule.refresh_ical_file()
schedule.refresh_ical_final_schedule()

select_input = _get_users_select(
organization=organization, input_id_prefix="test", action_id="test", max_options_per_group=2
Expand Down
2 changes: 2 additions & 0 deletions engine/apps/slack/tests/test_scenario_steps/test_paging.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ def test_add_user_no_warning(make_organization_and_user_with_slack_identities, m
)
on_call_shift.add_rolling_users([[user]])
schedule.refresh_ical_file()
schedule.refresh_ical_final_schedule()

payload = make_paging_view_slack_payload(selected_org=organization, user=user)

Expand Down Expand Up @@ -221,6 +222,7 @@ def test_add_user_maximum_exceeded(make_organization_and_user_with_slack_identit
)
on_call_shift.add_rolling_users([[user]])
schedule.refresh_ical_file()
schedule.refresh_ical_final_schedule()

payload = make_paging_view_slack_payload(selected_org=organization, user=user)

Expand Down
39 changes: 39 additions & 0 deletions grafana-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Changelog

## [1.9.27](https://github.com/grafana/irm/compare/grafana-oncall-app-v1.9.26...grafana-oncall-app-v1.9.27) (2024-09-26)


### Bug Fixes

* address RBAC Admin issue ([#183](https://github.com/grafana/irm/issues/183)) ([508857b](https://github.com/grafana/irm/commit/508857b719641ce405910bb1b110dec62f1a7af5))
* Bring webhooks to IRM ([#144](https://github.com/grafana/irm/issues/144)) ([1a883e1](https://github.com/grafana/irm/commit/1a883e1e44fe154ec0a7d36fa8183444fb90c773))
* run go mod tidy ([#174](https://github.com/grafana/irm/issues/174)) ([df2cf75](https://github.com/grafana/irm/commit/df2cf75ac5d4f57661af722f4785ef4996644bbc))
* style links in incident message ([#143](https://github.com/grafana/irm/issues/143)) ([2e55b07](https://github.com/grafana/irm/commit/2e55b07c1069cebfb30ba944b1c0b6f7dbfb1bad))


### Dependencies

* bump `github.com/grafana/grafana-plugin-sdk-go` to `v0.250.2` to address CVE-2024-8986 ([#173](https://github.com/grafana/irm/issues/173)) ([2385dc3](https://github.com/grafana/irm/commit/2385dc39e0108ca8ee4047046a34a735d1598ec8))
* bump micromatch version from 4.0.6 to 4.0.8 ([#181](https://github.com/grafana/irm/issues/181)) ([b1123fd](https://github.com/grafana/irm/commit/b1123fd8d54db080eb90c9959494a3bd00a89540))


### Miscellaneous Chores

* release main ([#157](https://github.com/grafana/irm/issues/157)) ([1b2901c](https://github.com/grafana/irm/commit/1b2901c952cc8e82f94becfa44db146fc0abe076))
* release main ([#187](https://github.com/grafana/irm/issues/187)) ([3221340](https://github.com/grafana/irm/commit/3221340148ac972ed32cd16194a5eaf3cc29db3d))
* release main ([#190](https://github.com/grafana/irm/issues/190)) ([e2489d0](https://github.com/grafana/irm/commit/e2489d0a31c4ac80dc72dca57c42eb3068fa4661))

## [1.9.26](https://github.com/grafana/irm/compare/grafana-oncall-app-v1.9.25...grafana-oncall-app-v1.9.26) (2024-09-23)


### Bug Fixes

* fix template editor layout ([#142](https://github.com/grafana/irm/issues/142)) ([c8ac3b0](https://github.com/grafana/irm/commit/c8ac3b0f60cb5472fb93b59255ca30bc8ba64653))
* make sure GMT elem is selected from the dropdown options is sele… ([#141](https://github.com/grafana/irm/issues/141)) ([cc86f17](https://github.com/grafana/irm/commit/cc86f1751f7378d981d6e60a20cef746f090f1df))
* rename OnCall notification titles ([#126](https://github.com/grafana/irm/issues/126)) ([7df0120](https://github.com/grafana/irm/commit/7df01208271b29640939730375d035b5d5a13f98))
* update how config page is rendered in cloud ([#137](https://github.com/grafana/irm/issues/137)) ([3cf9bc2](https://github.com/grafana/irm/commit/3cf9bc23bee92dd8dde77fe225efebaeaf38a233))


### Miscellaneous Chores

* improve (again) ui pod readiness probe ([#120](https://github.com/grafana/irm/issues/120)) ([c4ee02b](https://github.com/grafana/irm/commit/c4ee02b5253a7cfaf983518c6475f6207a66e253))
6 changes: 4 additions & 2 deletions grafana-plugin/e2e-tests/utils/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@ export const sendDemoAlert = async (page: Page): Promise<void> => {
export const createIntegrationAndSendDemoAlert = async (
page: Page,
integrationName: string,
escalationChainName: string
escalationChainName?: string
): Promise<void> => {
await createIntegration({ page, integrationName });
await assignEscalationChainToIntegration(page, escalationChainName);
if (escalationChainName) {
await assignEscalationChainToIntegration(page, escalationChainName);
}
await sendDemoAlert(page);
};

Expand Down
Loading

0 comments on commit 75e8374

Please sign in to comment.