commit
2e924efd74
8 changed files with 72 additions and 36 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -60,14 +60,19 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
|
|||
query: { id },
|
||||
} = this.props;
|
||||
|
||||
if (id) {
|
||||
const outgoingWebhook = await store.outgoingWebhookStore
|
||||
if (!id) {return;}
|
||||
|
||||
let outgoingWebhook: OutgoingWebhook | void = undefined;
|
||||
const isNewWebhook = id === 'new';
|
||||
|
||||
if (!isNewWebhook) {
|
||||
outgoingWebhook = await store.outgoingWebhookStore
|
||||
.loadItem(id, true)
|
||||
.catch((error) => this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } }));
|
||||
}
|
||||
|
||||
if (outgoingWebhook) {
|
||||
this.setState({ outgoingWebhookIdToEdit: id });
|
||||
}
|
||||
if (outgoingWebhook || isNewWebhook) {
|
||||
this.setState({ outgoingWebhookIdToEdit: id });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,13 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
|
|||
query: { id },
|
||||
} = this.props;
|
||||
|
||||
if (id) {
|
||||
if (!id) {return;}
|
||||
|
||||
let scheduleId: string = undefined;
|
||||
const isNewSchedule = id === 'new';
|
||||
|
||||
if (!isNewSchedule) {
|
||||
// load schedule only for valid id
|
||||
const schedule = await store.scheduleStore
|
||||
.loadItem(id, true)
|
||||
.catch((error) => this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } }));
|
||||
|
|
@ -93,13 +99,13 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
|
|||
return;
|
||||
}
|
||||
|
||||
const schedules = store.scheduleStore.getSearchResult();
|
||||
const scheduleId = schedules && schedules.find((res) => 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 = schedule.id;
|
||||
}
|
||||
|
||||
if (scheduleId || isNewSchedule) {
|
||||
this.setState({ scheduleIdToEdit: id });
|
||||
} else {
|
||||
openErrorNotification(`Schedule with id=${id} is not found. Please select schedule from the list.`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue