From 5e272f85657009123650be6c76861a6712a956f3 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Mon, 3 Oct 2022 11:22:02 +0100 Subject: [PATCH 1/6] Fix failing tests due to bug in month calculations (#599) --- .../schedules/models/custom_on_call_shift.py | 11 ++++++- .../tests/test_custom_on_call_shift.py | 32 +++++++++---------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/engine/apps/schedules/models/custom_on_call_shift.py b/engine/apps/schedules/models/custom_on_call_shift.py index 232c824a..7b24676a 100644 --- a/engine/apps/schedules/models/custom_on_call_shift.py +++ b/engine/apps/schedules/models/custom_on_call_shift.py @@ -5,6 +5,7 @@ from calendar import monthrange from uuid import uuid4 import pytz +from dateutil import relativedelta from django.apps import apps from django.conf import settings from django.core.validators import MinLengthValidator @@ -353,6 +354,13 @@ class CustomOnCallShift(models.Model): ONE_DAY = 1 ONE_HOUR = 1 + def add_months(year, month, months_add): + """ + Utility method for month calculation. E.g. (2022, 12) + 1 month = (2023, 1) + """ + dt = timezone.datetime.min.replace(year=year, month=month) + relativedelta.relativedelta(months=months_add) + return dt.year, dt.month + current_event = Event.from_ical(event_ical) # take shift interval, not event interval. For rolling_users shift it is not the same. interval = self.interval or 1 @@ -385,7 +393,8 @@ class CustomOnCallShift(models.Model): days_for_next_event = DAYS_IN_A_MONTH - current_event_start.day + ONE_DAY # count next event start date with respect to event interval for i in range(1, interval): - next_month_days = monthrange(current_event_start.year, current_event_start.month + i)[1] + year, month = add_months(current_event_start.year, current_event_start.month, i) + next_month_days = monthrange(year, month)[1] days_for_next_event += next_month_days next_event_start = current_event_start + timezone.timedelta(days=days_for_next_event) diff --git a/engine/apps/schedules/tests/test_custom_on_call_shift.py b/engine/apps/schedules/tests/test_custom_on_call_shift.py index dd3cfba6..4dbf6029 100644 --- a/engine/apps/schedules/tests/test_custom_on_call_shift.py +++ b/engine/apps/schedules/tests/test_custom_on_call_shift.py @@ -354,11 +354,11 @@ def test_rolling_users_event_with_interval_monthly( user_2 = make_user_for_organization(organization) schedule = make_schedule(organization, schedule_class=OnCallScheduleCalendar) - start_date = timezone.now().replace(day=1, microsecond=0) - days_for_next_month_1 = monthrange(start_date.year, start_date.month)[1] - days_for_next_month_2 = monthrange(start_date.year, start_date.month + 1)[1] + days_for_next_month_1 - days_for_next_month_3 = monthrange(start_date.year, start_date.month + 2)[1] + days_for_next_month_2 - days_for_next_month_4 = monthrange(start_date.year, start_date.month + 3)[1] + days_for_next_month_3 + start_date = timezone.datetime(year=2022, month=10, day=1, hour=10, minute=30) + days_for_next_month_1 = monthrange(2022, 10)[1] + days_for_next_month_2 = monthrange(2022, 11)[1] + days_for_next_month_1 + days_for_next_month_3 = monthrange(2022, 12)[1] + days_for_next_month_2 + days_for_next_month_4 = monthrange(2023, 1)[1] + days_for_next_month_3 data = { "priority_level": 1, @@ -718,19 +718,19 @@ def test_rolling_users_with_diff_start_and_rotation_start_monthly( user_3 = make_user_for_organization(organization) schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb) - now = timezone.now().replace(day=1, microsecond=0) - days_in_curr_month = monthrange(now.year, now.month)[1] - days_in_next_month = monthrange(now.year, now.month + 1)[1] + start_date = timezone.datetime(year=2022, month=12, day=1, hour=10, minute=30) + days_in_curr_month = monthrange(2022, 12)[1] + days_in_next_month = monthrange(2023, 1)[1] data = { "priority_level": 1, - "start": now, - "week_start": now.weekday(), - "rotation_start": now + timezone.timedelta(days=days_in_curr_month - 1, hours=1), + "start": start_date, + "week_start": start_date.weekday(), + "rotation_start": start_date + timezone.timedelta(days=days_in_curr_month - 1, hours=1), "duration": timezone.timedelta(seconds=1800), "frequency": CustomOnCallShift.FREQUENCY_MONTHLY, "schedule": schedule, - "until": now + timezone.timedelta(days=days_in_curr_month + days_in_next_month + 10, minutes=1), + "until": start_date + timezone.timedelta(days=days_in_curr_month + days_in_next_month + 10, minutes=1), } rolling_users = [[user_1], [user_2], [user_3]] on_call_shift = make_on_call_shift( @@ -738,7 +738,7 @@ def test_rolling_users_with_diff_start_and_rotation_start_monthly( ) on_call_shift.add_rolling_users(rolling_users) - date = now + timezone.timedelta(minutes=5) + date = start_date + timezone.timedelta(minutes=5) # rotation starts from user_2, because user_1 started earlier than rotation start date user_2_on_call_dates = [date + timezone.timedelta(days=days_in_curr_month)] user_3_on_call_dates = [date + timezone.timedelta(days=days_in_curr_month + days_in_next_month)] @@ -774,9 +774,9 @@ def test_rolling_users_with_diff_start_and_rotation_start_monthly_by_monthday( user_3 = make_user_for_organization(organization) schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb) - start_date = timezone.now().replace(day=1, microsecond=0) - days_in_curr_month = monthrange(start_date.year, start_date.month)[1] - days_in_next_month = monthrange(start_date.year, start_date.month + 1)[1] + start_date = timezone.datetime(year=2022, month=12, day=1, hour=10, minute=30) + days_in_curr_month = monthrange(2022, 12)[1] + days_in_next_month = monthrange(2023, 1)[1] data = { "priority_level": 1, From f8314ef9c2852ee0326930a5edae65a6094034d3 Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Mon, 3 Oct 2022 07:37:59 -0300 Subject: [PATCH 2/6] Fix timing issue with schedule tests reusing cached users (#592) Co-authored-by: Vadim Stepanov --- engine/apps/api/tests/test_schedules.py | 17 +++++++++++------ .../schedules/tests/test_on_call_schedule.py | 3 +++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/engine/apps/api/tests/test_schedules.py b/engine/apps/api/tests/test_schedules.py index 7139f2e9..19537391 100644 --- a/engine/apps/api/tests/test_schedules.py +++ b/engine/apps/api/tests/test_schedules.py @@ -10,6 +10,7 @@ from rest_framework.serializers import ValidationError from rest_framework.test import APIClient from apps.alerts.models import EscalationPolicy +from apps.schedules.ical_utils import memoized_users_in_ical from apps.schedules.models import ( CustomOnCallShift, OnCallSchedule, @@ -742,6 +743,8 @@ def test_filter_events_final_schedule( request_date = start_date user_a, user_b, user_c, user_d, user_e = (make_user_for_organization(organization, username=i) for i in "ABCDE") + # clear users pks <-> organization cache (persisting between tests) + memoized_users_in_ical.cache_clear() shifts = ( # user, priority, start time (h), duration (hs) @@ -837,7 +840,7 @@ def test_next_shifts_per_user( make_schedule, make_on_call_shift, ): - organization, user, token = make_organization_and_user_with_plugin_token() + organization, admin, token = make_organization_and_user_with_plugin_token() client = APIClient() schedule = make_schedule( @@ -848,6 +851,8 @@ def test_next_shifts_per_user( tomorrow = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0) + timezone.timedelta(days=1) user_a, user_b, user_c, user_d = (make_user_for_organization(organization, username=i) for i in "ABCD") + # clear users pks <-> organization cache (persisting between tests) + memoized_users_in_ical.cache_clear() shifts = ( # user, priority, start time (h), duration (hs) @@ -860,16 +865,16 @@ def test_next_shifts_per_user( for user, priority, start_h, duration in shifts: data = { "start": tomorrow + timezone.timedelta(hours=start_h), - "rotation_start": tomorrow, + "rotation_start": tomorrow + timezone.timedelta(hours=start_h), "duration": timezone.timedelta(hours=duration), "priority_level": priority, "frequency": CustomOnCallShift.FREQUENCY_DAILY, "schedule": schedule, } on_call_shift = make_on_call_shift( - organization=organization, shift_type=CustomOnCallShift.TYPE_RECURRENT_EVENT, **data + organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data ) - on_call_shift.users.add(user) + on_call_shift.add_rolling_users([[user]]) # override in the past: 17-18 / D # won't be listed, but user D will still be included in the response @@ -896,10 +901,10 @@ def test_next_shifts_per_user( ) override.add_rolling_users([[user_c]]) - # final sdhedule: 7-12: B, 15-16: A, 16-17: B, 17-18: C (override), 18-20: C + # final schedule: 7-12: B, 15-16: A, 16-17: B, 17-18: C (override), 18-20: C 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(user, token)) + response = client.get(url, format="json", **make_user_auth_headers(admin, token)) assert response.status_code == status.HTTP_200_OK expected = { diff --git a/engine/apps/schedules/tests/test_on_call_schedule.py b/engine/apps/schedules/tests/test_on_call_schedule.py index e8da0e67..bb9699e7 100644 --- a/engine/apps/schedules/tests/test_on_call_schedule.py +++ b/engine/apps/schedules/tests/test_on_call_schedule.py @@ -4,6 +4,7 @@ import pytest import pytz from django.utils import timezone +from apps.schedules.ical_utils import memoized_users_in_ical from apps.schedules.models import CustomOnCallShift, OnCallSchedule, OnCallScheduleCalendar, OnCallScheduleWeb from common.constants.role import Role @@ -236,6 +237,8 @@ def test_filter_events_ical_all_day(make_organization, make_user_for_organizatio schedule.cached_ical_file_primary = calendar.to_ical() for u in ("@Bernard Desruisseaux", "@Bob", "@Alex"): make_user_for_organization(organization, username=u) + # clear users pks <-> organization cache (persisting between tests) + memoized_users_in_ical.cache_clear() day_to_check_iso = "2021-01-27T15:27:14.448059+00:00" parsed_iso_day_to_check = datetime.datetime.fromisoformat(day_to_check_iso).replace(tzinfo=pytz.UTC) From dd55c5a9f9bbb70b0b21fe58d213d49c3f8bc6d4 Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Mon, 3 Oct 2022 15:14:14 +0300 Subject: [PATCH 3/6] schedules fix --- .../src/pages/schedules/Schedules.tsx | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index 230d2c84..cb7b1655 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -85,7 +85,13 @@ class SchedulesPage extends React.Component this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } })); @@ -94,12 +100,13 @@ class SchedulesPage extends React.Component res.id === id)?.id; - if (scheduleId || id === 'new') { - this.setState({ scheduleIdToEdit: id }); - } else { - openErrorNotification(`Schedule with id=${id} is not found. Please select schedule from the list.`); - } + scheduleId = schedules && schedules.find((res) => res.id === id)?.id; + } + + if (scheduleId || isNewSchedule) { + this.setState({ scheduleIdToEdit: id }); + } else { + openErrorNotification(`Schedule with id=${id} is not found. Please select schedule from the list.`); } }; @@ -165,7 +172,7 @@ class SchedulesPage extends React.Component {() => ( <> From c7d02deaf898ed5b798f79be9ae598258ebb3e28 Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Mon, 3 Oct 2022 15:19:24 +0300 Subject: [PATCH 4/6] remove unused linter --- grafana-plugin/src/pages/schedules/Schedules.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index cb7b1655..9493d837 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -85,7 +85,7 @@ class SchedulesPage extends React.Component {() => ( <> From 6338e5878398715eda782aa6305bae75f13dd561 Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Mon, 3 Oct 2022 17:25:13 +0300 Subject: [PATCH 5/6] fix for webhook linter --- .../pages/outgoing_webhooks/OutgoingWebhooks.tsx | 15 ++++++++++----- grafana-plugin/src/pages/schedules/Schedules.tsx | 5 ++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/grafana-plugin/src/pages/outgoing_webhooks/OutgoingWebhooks.tsx b/grafana-plugin/src/pages/outgoing_webhooks/OutgoingWebhooks.tsx index 8ddd846c..51d80e98 100644 --- a/grafana-plugin/src/pages/outgoing_webhooks/OutgoingWebhooks.tsx +++ b/grafana-plugin/src/pages/outgoing_webhooks/OutgoingWebhooks.tsx @@ -60,14 +60,19 @@ class OutgoingWebhooks extends React.Component this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } })); + } - if (outgoingWebhook) { - this.setState({ outgoingWebhookIdToEdit: id }); - } + if (outgoingWebhook || isNewWebhook) { + this.setState({ outgoingWebhookIdToEdit: id }); } }; diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index 9493d837..3fb0d618 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -87,8 +87,8 @@ class SchedulesPage extends React.Component res.id === id)?.id; + scheduleId = schedule.id; } if (scheduleId || isNewSchedule) { From 0ee8f3e42b3833112f70dc5d0b80e5d5a30865f6 Mon Sep 17 00:00:00 2001 From: Michael Derynck Date: Mon, 3 Oct 2022 11:18:49 -0600 Subject: [PATCH 6/6] Update changelog for v1.0.39 --- CHANGELOG.md | 4 ++++ grafana-plugin/CHANGELOG.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0ead17d..ebfa6fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## v1.0.39 (2022-10-03) + +- Fix issue in v1.0.38 blocking the creation of schedules and webhooks in the UI + ## v1.0.38 (2022-09-30) - Fix exception handling for adding resolution notes when slack and oncall users are out of sync. diff --git a/grafana-plugin/CHANGELOG.md b/grafana-plugin/CHANGELOG.md index d0ead17d..ebfa6fb2 100644 --- a/grafana-plugin/CHANGELOG.md +++ b/grafana-plugin/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## v1.0.39 (2022-10-03) + +- Fix issue in v1.0.38 blocking the creation of schedules and webhooks in the UI + ## v1.0.38 (2022-09-30) - Fix exception handling for adding resolution notes when slack and oncall users are out of sync.