parent
7bd6f0d095
commit
4149c266bb
9 changed files with 156 additions and 7 deletions
|
|
@ -34,6 +34,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## v1.2.10 (2023-04-13)
|
||||
|
||||
### Added
|
||||
|
||||
- Added mine filter to schedules listing
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed a bug in GForm's RemoteSelect where the value for Dropdown could not change
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ ICAL_URL = "https://calendar.google.com/calendar/ical/amixr.io_37gttuakhrtr75ano
|
|||
@pytest.fixture()
|
||||
def schedule_internal_api_setup(
|
||||
make_organization_and_user_with_plugin_token,
|
||||
make_user_auth_headers,
|
||||
make_slack_channel,
|
||||
make_schedule,
|
||||
):
|
||||
|
|
@ -377,6 +376,50 @@ def test_get_list_schedules_by_used(
|
|||
assert set(schedule_names) == set(expected_schedule_names)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"query_param, expected_schedule_names",
|
||||
[
|
||||
("?mine=true", ["test_web_schedule"]),
|
||||
("?mine=false", ["test_calendar_schedule", "test_ical_schedule", "test_web_schedule"]),
|
||||
("?mine=null", ["test_calendar_schedule", "test_ical_schedule", "test_web_schedule"]),
|
||||
("", ["test_calendar_schedule", "test_ical_schedule", "test_web_schedule"]),
|
||||
],
|
||||
)
|
||||
def test_get_list_schedules_by_mine(
|
||||
schedule_internal_api_setup,
|
||||
make_user_auth_headers,
|
||||
make_on_call_shift,
|
||||
query_param,
|
||||
expected_schedule_names,
|
||||
):
|
||||
user, token, calendar_schedule, ical_schedule, web_schedule, slack_channel = schedule_internal_api_setup
|
||||
client = APIClient()
|
||||
|
||||
today = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
# setup user shift in web schedule
|
||||
override_data = {
|
||||
"start": today + timezone.timedelta(hours=22),
|
||||
"rotation_start": today + timezone.timedelta(hours=22),
|
||||
"duration": timezone.timedelta(hours=1),
|
||||
"schedule": web_schedule,
|
||||
}
|
||||
override = make_on_call_shift(
|
||||
organization=user.organization, shift_type=CustomOnCallShift.TYPE_OVERRIDE, **override_data
|
||||
)
|
||||
override.add_rolling_users([[user]])
|
||||
web_schedule.refresh_ical_file()
|
||||
|
||||
url = reverse("api-internal:schedule-list") + query_param
|
||||
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()["count"] == len(expected_schedule_names)
|
||||
|
||||
schedule_names = [schedule["name"] for schedule in response.json()["results"]]
|
||||
assert set(schedule_names) == set(expected_schedule_names)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_list_schedules_pagination_respects_search(
|
||||
schedule_internal_api_setup,
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ class ScheduleView(
|
|||
def get_queryset(self, ignore_filtering_by_available_teams=False):
|
||||
is_short_request = self.request.query_params.get("short", "false") == "true"
|
||||
filter_by_type = self.request.query_params.get("type")
|
||||
mine = BooleanField(allow_null=True).to_internal_value(data=self.request.query_params.get("mine"))
|
||||
used = BooleanField(allow_null=True).to_internal_value(data=self.request.query_params.get("used"))
|
||||
organization = self.request.auth.organization
|
||||
queryset = OnCallSchedule.objects.filter(organization=organization).defer(
|
||||
|
|
@ -178,6 +179,9 @@ class ScheduleView(
|
|||
queryset = queryset.filter().instance_of(SCHEDULE_TYPE_TO_CLASS[filter_by_type])
|
||||
if used is not None:
|
||||
queryset = queryset.filter(escalation_policies__isnull=not used).distinct()
|
||||
if mine:
|
||||
user = self.request.user
|
||||
queryset = queryset.related_to_user(user)
|
||||
|
||||
queryset = queryset.order_by("pk")
|
||||
return queryset
|
||||
|
|
@ -475,6 +479,12 @@ class ScheduleView(
|
|||
"href": api_root + "teams/",
|
||||
"global": True,
|
||||
},
|
||||
{
|
||||
"name": "mine",
|
||||
"type": "boolean",
|
||||
"display_name": "Mine",
|
||||
"default": "true",
|
||||
},
|
||||
{
|
||||
"name": "used",
|
||||
"type": "boolean",
|
||||
|
|
|
|||
|
|
@ -74,11 +74,12 @@ class OnCallScheduleQuerySet(PolymorphicQuerySet):
|
|||
return get_oncall_users_for_multiple_schedules(self, events_datetime)
|
||||
|
||||
def related_to_user(self, user):
|
||||
username_regex = r"SUMMARY:(\[L[0-9]+\] )?{}".format(user.username)
|
||||
return self.filter(
|
||||
Q(cached_ical_file_primary__contains=user.username)
|
||||
Q(cached_ical_file_primary__regex=username_regex)
|
||||
| Q(cached_ical_file_primary__contains=user.email)
|
||||
| Q(cached_ical_file_overrides__contains=user.username)
|
||||
| Q(cached_ical_file_overrides__contains=user.username),
|
||||
| Q(cached_ical_file_overrides__regex=username_regex)
|
||||
| Q(cached_ical_file_overrides__contains=user.email),
|
||||
organization=user.organization,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1199,13 +1199,78 @@ def test_user_related_schedules(
|
|||
override.add_rolling_users([[admin]])
|
||||
schedule2.refresh_ical_file()
|
||||
|
||||
# schedule2
|
||||
# schedule3
|
||||
make_schedule(organization, schedule_class=OnCallScheduleWeb)
|
||||
|
||||
schedules = OnCallSchedule.objects.related_to_user(admin)
|
||||
assert list(schedules) == [schedule1, schedule2]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_user_related_schedules_only_username(
|
||||
make_organization,
|
||||
make_user_for_organization,
|
||||
make_schedule,
|
||||
make_on_call_shift,
|
||||
):
|
||||
organization = make_organization()
|
||||
# oncall is used as keyword in the ical calendar definition,
|
||||
# shouldn't be associated to the user
|
||||
user = make_user_for_organization(organization, username="oncall")
|
||||
other_user = make_user_for_organization(organization, username="other")
|
||||
|
||||
today = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
schedule1 = make_schedule(organization, schedule_class=OnCallScheduleWeb)
|
||||
shifts = (
|
||||
# user, priority, start time (h), duration (seconds)
|
||||
(user, 1, 0, (24 * 60 * 60) - 1), # r1-1: 0-23:59:59
|
||||
)
|
||||
for user, priority, start_h, duration in shifts:
|
||||
data = {
|
||||
"start": today + timezone.timedelta(hours=start_h),
|
||||
"rotation_start": today + timezone.timedelta(hours=start_h),
|
||||
"duration": timezone.timedelta(seconds=duration),
|
||||
"priority_level": priority,
|
||||
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
|
||||
"schedule": schedule1,
|
||||
}
|
||||
on_call_shift = make_on_call_shift(
|
||||
organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
|
||||
)
|
||||
on_call_shift.add_rolling_users([[user]])
|
||||
schedule1.refresh_ical_file()
|
||||
|
||||
schedule2 = make_schedule(organization, schedule_class=OnCallScheduleWeb)
|
||||
override_data = {
|
||||
"start": today + timezone.timedelta(hours=22),
|
||||
"rotation_start": today + timezone.timedelta(hours=22),
|
||||
"duration": timezone.timedelta(hours=1),
|
||||
"schedule": schedule2,
|
||||
}
|
||||
override = make_on_call_shift(
|
||||
organization=organization, shift_type=CustomOnCallShift.TYPE_OVERRIDE, **override_data
|
||||
)
|
||||
override.add_rolling_users([[user]])
|
||||
schedule2.refresh_ical_file()
|
||||
|
||||
# schedule3
|
||||
schedule3 = make_schedule(organization, schedule_class=OnCallScheduleWeb)
|
||||
override_data = {
|
||||
"start": today + timezone.timedelta(hours=22),
|
||||
"rotation_start": today + timezone.timedelta(hours=22),
|
||||
"duration": timezone.timedelta(hours=1),
|
||||
"schedule": schedule3,
|
||||
}
|
||||
override = make_on_call_shift(
|
||||
organization=organization, shift_type=CustomOnCallShift.TYPE_OVERRIDE, **override_data
|
||||
)
|
||||
override.add_rolling_users([[other_user]])
|
||||
schedule3.refresh_ical_file()
|
||||
|
||||
schedules = OnCallSchedule.objects.related_to_user(user)
|
||||
assert list(schedules) == [schedule1, schedule2]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_upcoming_shift_for_user(
|
||||
make_organization,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,14 @@ const SchedulesFilters = (props: SchedulesFiltersProps) => {
|
|||
},
|
||||
[value]
|
||||
);
|
||||
|
||||
const handleMineChange = useCallback(
|
||||
(mine) => {
|
||||
onChange({ ...value, mine });
|
||||
},
|
||||
[value]
|
||||
);
|
||||
|
||||
const handleStatusChange = useCallback(
|
||||
(used) => {
|
||||
onChange({ ...value, used });
|
||||
|
|
@ -53,6 +61,23 @@ const SchedulesFilters = (props: SchedulesFiltersProps) => {
|
|||
</Field>
|
||||
</div>
|
||||
<div className={cx('right')}>
|
||||
<Field label="Mine">
|
||||
<RadioButtonGroup
|
||||
options={[
|
||||
{ label: 'All', value: undefined },
|
||||
{
|
||||
label: 'Mine',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'Not mine',
|
||||
value: false,
|
||||
},
|
||||
]}
|
||||
value={value.mine}
|
||||
onChange={handleMineChange}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Status">
|
||||
<RadioButtonGroup
|
||||
options={[
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ export interface SchedulesFiltersType {
|
|||
searchTerm: string;
|
||||
type: ScheduleType;
|
||||
used: boolean | undefined;
|
||||
mine: boolean | undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ export class ScheduleStore extends BaseStore {
|
|||
|
||||
@action
|
||||
async updateItems(
|
||||
f: SchedulesFiltersType | string = { searchTerm: '', type: undefined, used: undefined },
|
||||
f: SchedulesFiltersType | string = { searchTerm: '', type: undefined, used: undefined, mine: undefined },
|
||||
page = 1,
|
||||
shouldUpdateFn: () => boolean = undefined
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
|
|||
|
||||
this.state = {
|
||||
startMoment: getStartOfWeek(store.currentTimezone),
|
||||
filters: { searchTerm: '', type: undefined, used: undefined },
|
||||
filters: { searchTerm: '', type: undefined, used: undefined, mine: undefined },
|
||||
showNewScheduleSelector: false,
|
||||
expandedRowKeys: [],
|
||||
scheduleIdToEdit: undefined,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue