Add observer-related GraphQL fields

Add the associatedUser field to the enrollment type, and the ability to
filter a course's enrollmentsConnection by specific enrollment types and
observed user IDs. This allows us to return only enrollments for
observers observing specific students.

closes EVAL-2059
flag=none

Test plan:
- Have a course with at least two observer enrollments, and various
  other types of enrollments
- Take note of the ID of one of the students being observed
- In GraphiQL or your preferred testing platform, run a query like

query MyQuery {
  course(id: <course ID>) {
    enrollmentsConnection(filter: {
      associatedUserIds: [<observed student ID>]
      types: [ObserverEnrollment]
    }) {
      nodes {
        _id
        type
        user {
          _id
          name
        }
        associatedUser {
          _id
          name
        }
      }
    }
  }
}

- Check that you only receive observer enrollments observing the user ID
  you specified
  - Check that the associatedUser field for observer enrollments contains
    the id/name of the observed user and is null for other enrollment
    types
- Check that the "types" filter option does the right thing without
  specifying any associatedUserIds; e.g., test that asking for
  types: [StudentEnrollment, ObserverEnrollment] returns only student
  and observer enrollments

Change-Id: Ic0be3f6006e84202e017750fe0d3adde9ade2fdc
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/276775
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Product-Review: Syed Hussain <shussain@instructure.com>
Reviewed-by: Spencer Olson <solson@instructure.com>
Reviewed-by: Kai Bjorkman <kbjorkman@instructure.com>
QA-Review: Kai Bjorkman <kbjorkman@instructure.com>
This commit is contained in:
Adrian Packel 2021-10-25 15:31:30 -05:00
parent 221cbf2b96
commit b0b0074d22
4 changed files with 71 additions and 4 deletions

View File

@ -163,15 +163,19 @@ module Types
scope
end
field :enrollments_connection, EnrollmentType.connection_type, null: true
def enrollments_connection
field :enrollments_connection, EnrollmentType.connection_type, null: true do
argument :filter, EnrollmentFilterInputType, required: false
end
def enrollments_connection(filter: {})
return nil unless course.grants_any_right?(
current_user, session,
:read_roster, :view_all_grades, :manage_grades
)
course.apply_enrollment_visibility(course.all_enrollments,
current_user).active
scope = course.apply_enrollment_visibility(course.all_enrollments, current_user).active
scope = scope.where(associated_user_id: filter[:associated_user_ids]) if filter[:associated_user_ids].present?
scope = scope.where(type: filter[:types]) if filter[:types].present?
scope
end
field :grading_periods_connection, GradingPeriodType.connection_type, null: true

View File

@ -40,6 +40,16 @@ module Types
value "StudentViewEnrollment"
end
class EnrollmentFilterInputType < Types::BaseInputObject
graphql_name "EnrollmentFilterInput"
argument :types, [EnrollmentTypeType], required: false, default_value: nil
argument :associated_user_ids, [ID],
prepare: GraphQLHelpers.relay_or_legacy_ids_prepare_func("User"),
required: false,
default_value: []
end
class EnrollmentType < ApplicationObjectType
graphql_name "Enrollment"
@ -57,6 +67,11 @@ module Types
load_association(:user)
end
field :associated_user, UserType, null: true
def associated_user
load_association(:associated_user)
end
field :course, CourseType, null: true
def course
load_association(:course)

View File

@ -418,6 +418,39 @@ describe Types::CourseType do
other_teacher.enrollments.first.id.to_s,
]
end
describe "filtering" do
it "returns only enrollments of the specified types if included" do
ta_enrollment = course.enroll_ta(User.create!, enrollment_state: :active)
expect(
course_type.resolve(
"enrollmentsConnection(filter: {types: [TeacherEnrollment, TaEnrollment]}) { nodes { _id } }",
current_user: @teacher
)
).to match_array([
@teacher.enrollments.first.id.to_s,
other_teacher.enrollments.first.id.to_s,
ta_enrollment.id.to_s
])
end
it "returns only enrollments with the specified associated_user_ids if included" do
observer = User.create!
observer_enrollment = observer_in_course(course: @course, user: observer)
observer_enrollment.update!(associated_user: @student1)
other_observer_enrollment = observer_in_course(course: @course, user: observer)
other_observer_enrollment.update!(associated_user: @student2)
expect(
course_type.resolve(
"enrollmentsConnection(filter: {associatedUserIds: [#{@student1.id}]}) { nodes { _id } }",
current_user: @teacher
)
).to eq [observer_enrollment.id.to_s]
end
end
end
end

View File

@ -118,4 +118,19 @@ describe Types::EnrollmentType do
).to eq enrollment.course_section.id.to_s
end
end
describe "associated_user" do
it "returns the associated user when one exists" do
observer = User.create!
observer_enrollment = observer_in_course(course: @course, user: observer)
observer_enrollment.update!(associated_user: @student)
tester = GraphQLTypeTester.new(observer_enrollment, current_user: @observer)
expect(tester.resolve("associatedUser { _id }")).to eq @student.id.to_s
end
it "returns nil when no associated user exists" do
expect(enrollment_type.resolve("associatedUser { _id }")).to be nil
end
end
end