Add mine filter to schedules listing (#1743)

Related to #1741 .
This commit is contained in:
Matias Bordese 2023-04-18 10:16:36 -03:00 committed by GitHub
parent 7bd6f0d095
commit 4149c266bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 156 additions and 7 deletions

View file

@ -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

View file

@ -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,

View file

@ -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",

View file

@ -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,
)

View file

@ -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,

View file

@ -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={[

View file

@ -4,4 +4,5 @@ export interface SchedulesFiltersType {
searchTerm: string;
type: ScheduleType;
used: boolean | undefined;
mine: boolean | undefined;
}

View file

@ -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
) {

View file

@ -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,