graphql: add submission type
closes CNVS-37581 Test plan: list the submissions in a course (make sure to test the studentIds and orderBy params) Change-Id: Ica59cb08981ce37fb7e61e83fe891edda73a0b40 Reviewed-on: https://gerrit.instructure.com/121084 Tested-by: Jenkins Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com> QA-Review: Collin Parrish <cparrish@instructure.com> Product-Review: Cameron Matheson <cameron@instructure.com>
This commit is contained in:
parent
f433ba9643
commit
7a821f6f32
|
@ -4,16 +4,38 @@ module GraphQLHelpers
|
|||
# will get a standard canvas id
|
||||
def self.relay_or_legacy_id_prepare_func(expected_type)
|
||||
Proc.new do |relay_or_legacy_id, ctx|
|
||||
if relay_or_legacy_id =~ /\A\d+\Z/
|
||||
relay_or_legacy_id
|
||||
else
|
||||
type, id = GraphQL::Schema::UniqueWithinType.decode(relay_or_legacy_id)
|
||||
if (type != expected_type || id.nil?)
|
||||
GraphQL::ExecutionError.new("expected an id for #{expected_type}")
|
||||
else
|
||||
id
|
||||
end
|
||||
begin
|
||||
self.parse_relay_or_legacy_id(relay_or_legacy_id, expected_type)
|
||||
rescue InvalidIDError => e
|
||||
GraphQL::ExecutionError.new(e.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.relay_or_legacy_ids_prepare_func(expected_type)
|
||||
Proc.new do |relay_or_legacy_ids, ctx|
|
||||
begin
|
||||
relay_or_legacy_ids.map { |relay_or_legacy_id, ctx|
|
||||
self.parse_relay_or_legacy_id(relay_or_legacy_id, expected_type)
|
||||
}
|
||||
rescue InvalidIDError => e
|
||||
GraphQL::ExecutionError.new(e.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.parse_relay_or_legacy_id(relay_or_legacy_id, expected_type)
|
||||
if relay_or_legacy_id =~ /\A\d+\Z/
|
||||
relay_or_legacy_id
|
||||
else
|
||||
type, id = GraphQL::Schema::UniqueWithinType.decode(relay_or_legacy_id)
|
||||
if (type != expected_type || id.nil?)
|
||||
raise InvalidIDError.new("expected an id for #{expected_type}")
|
||||
else
|
||||
id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class InvalidIDError < StandardError; end
|
||||
end
|
||||
|
|
|
@ -45,6 +45,55 @@ module Types
|
|||
GradingPeriod.for(course).order(:start_date)
|
||||
}
|
||||
end
|
||||
|
||||
connection :submissionsConnection, SubmissionType.connection_type do
|
||||
description "all the submissions for assignments in this course"
|
||||
|
||||
argument :studentIds, !types[types.ID], "Only return submissions for the given students.",
|
||||
prepare: GraphQLHelpers.relay_or_legacy_ids_prepare_func("User")
|
||||
argument :orderBy, types[SubmissionOrderInputType]
|
||||
|
||||
resolve ->(course, args, ctx) {
|
||||
current_user = ctx[:current_user]
|
||||
session = ctx[:session]
|
||||
user_ids = args[:studentIds].map(&:to_i)
|
||||
|
||||
if course.grants_any_right?(current_user, session, :manage_grades, :view_all_grades)
|
||||
# TODO: make a preloader for this???
|
||||
allowed_user_ids = course.apply_enrollment_visibility(course.all_student_enrollments, current_user).pluck(:user_id)
|
||||
allowed_user_ids &= user_ids
|
||||
elsif course.grants_right?(current_user, session, :read_grades)
|
||||
allowed_user_ids = user_ids & [current_user.id]
|
||||
else
|
||||
allowed_user_ids = []
|
||||
end
|
||||
|
||||
submissions = Submission.active.joins(:assignment).where(
|
||||
user_id: allowed_user_ids,
|
||||
assignment_id: course.assignments.published
|
||||
).where.not(workflow_state: "unsubmitted")
|
||||
|
||||
(args[:orderBy] || []).each { |order|
|
||||
submissions = submissions.order(order[:field] => order[:direction])
|
||||
}
|
||||
|
||||
submissions
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
SubmissionOrderInputType = GraphQL::InputObjectType.define do
|
||||
name "SubmissionOrderCriteria"
|
||||
argument :field, !GraphQL::EnumType.define {
|
||||
name "SubmissionOrderField"
|
||||
value "_id", value: "id"
|
||||
value "gradedAt", value: "graded_at"
|
||||
}
|
||||
argument :direction, GraphQL::EnumType.define {
|
||||
name "OrderDirection"
|
||||
value "ascending", value: "ASC"
|
||||
value "descending", value: "DESC"
|
||||
}
|
||||
end
|
||||
|
||||
CourseWorkflowState = GraphQL::EnumType.define do
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
module Types
|
||||
SubmissionType = GraphQL::ObjectType.define do
|
||||
name "Submission"
|
||||
|
||||
implements GraphQL::Relay::Node.interface
|
||||
global_id_field :id
|
||||
# not doing a legacy canvas id since they aren't used in the rest api
|
||||
|
||||
field :assignment, AssignmentType,
|
||||
resolve: ->(s, _, _) { Loaders::IDLoader.for(Assignment).load(s.assignment_id) }
|
||||
|
||||
field :user, UserType, resolve: ->(s, _, _) { Loaders::IDLoader.for(User).load(s.user_id) }
|
||||
|
||||
field :score, types.Float
|
||||
|
||||
field :grade, types.String
|
||||
|
||||
field :excused, types.Boolean,
|
||||
"excused assignments are ignored when calculating grades",
|
||||
property: :excused?
|
||||
|
||||
field :submittedAt, TimeType, property: :submitted_at
|
||||
field :gradedAt, TimeType, property: :graded_at
|
||||
end
|
||||
end
|
|
@ -46,4 +46,26 @@ describe GraphQLHelpers do
|
|||
).to be_a(GraphQL::ExecutionError)
|
||||
end
|
||||
end
|
||||
|
||||
context "relay_or_legacy_ids_prepare_func" do
|
||||
let :user1234 { "VXNlci0xMjM0" }
|
||||
let :user5678 { "VXNlci01Njc4" }
|
||||
let :ctx { nil }
|
||||
|
||||
it "works for valid ids" do
|
||||
expect(
|
||||
GraphQLHelpers.relay_or_legacy_ids_prepare_func("User").call(
|
||||
[user1234, user5678], nil
|
||||
)
|
||||
).to eq ["1234", "5678"]
|
||||
end
|
||||
|
||||
it "returns an error for bad ids" do
|
||||
expect(
|
||||
GraphQLHelpers.relay_or_legacy_ids_prepare_func("Course").call(
|
||||
[user1234, user5678], nil
|
||||
)
|
||||
).to be_a(GraphQL::ExecutionError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
|||
require File.expand_path(File.dirname(__FILE__) + '/../../helpers/graphql_type_tester')
|
||||
|
||||
describe Types::CourseType do
|
||||
let_once(:course) { Course.create! name: "TEST" }
|
||||
let_once(:course) { course_with_student(active_all: true); @course }
|
||||
let(:course_type) { GraphQLTypeTester.new(Types::CourseType, course) }
|
||||
|
||||
it "works" do
|
||||
|
@ -11,30 +11,85 @@ describe Types::CourseType do
|
|||
end
|
||||
|
||||
describe "assignmentsConnection" do
|
||||
let_once(:teacher) {
|
||||
teacher_in_course(active_all: true, course: course)
|
||||
@teacher
|
||||
}
|
||||
let_once(:student) {
|
||||
student_in_course(active_all: true, course: course)
|
||||
@student
|
||||
}
|
||||
let_once(:assignment) {
|
||||
course.assignments.create! name: "asdf", workflow_state: "unpublished"
|
||||
}
|
||||
|
||||
it "only returns visible assignments" do
|
||||
expect(course_type.assignmentsConnection(current_user: teacher).size).to eq 1
|
||||
expect(course_type.assignmentsConnection(current_user: student).size).to eq 0
|
||||
expect(course_type.assignmentsConnection(current_user: @teacher).size).to eq 1
|
||||
expect(course_type.assignmentsConnection(current_user: @student).size).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "sectionsConnection" do
|
||||
it "only includes active sections" do
|
||||
section1 = course.course_sections.create!(name: "Delete Me")
|
||||
section2 = course.course_sections.create!(name: "Keep Me")
|
||||
expect(course_type.sectionsConnection.size).to eq 2
|
||||
|
||||
section1.destroy
|
||||
expect(course_type.sectionsConnection.size).to eq 1
|
||||
end
|
||||
end
|
||||
|
||||
context "submissionsConnection" do
|
||||
before(:once) do
|
||||
a1 = course.assignments.create! name: "one", points_possible: 10
|
||||
a2 = course.assignments.create! name: "two", points_possible: 10
|
||||
|
||||
@student1 = @student
|
||||
student_in_course(active_all: true)
|
||||
@student2 = @student
|
||||
|
||||
@student1a1_submission, _ = a1.grade_student(@student1, grade: 1, grader: @teacher)
|
||||
@student1a2_submission, _ = a2.grade_student(@student1, grade: 9, grader: @teacher)
|
||||
@student2a1_submission, _ = a1.grade_student(@student2, grade: 5, grader: @teacher)
|
||||
|
||||
@student1a1_submission.update_attribute :graded_at, 4.days.ago
|
||||
@student1a2_submission.update_attribute :graded_at, 2.days.ago
|
||||
@student2a1_submission.update_attribute :graded_at, 3.days.ago
|
||||
end
|
||||
|
||||
it "returns submissions for specified students" do
|
||||
expect(
|
||||
course_type.submissionsConnection(
|
||||
current_user: @teacher,
|
||||
args: {
|
||||
studentIds: [@student1.id.to_s, @student2.id.to_s],
|
||||
orderBy: [{field: "id", direction: "asc"}],
|
||||
}
|
||||
)
|
||||
).to eq [
|
||||
@student1a1_submission,
|
||||
@student1a2_submission,
|
||||
@student2a1_submission
|
||||
].sort_by(&:id)
|
||||
end
|
||||
|
||||
it "doesn't let students see other student's submissions" do
|
||||
expect(
|
||||
course_type.submissionsConnection(
|
||||
current_user: @student2,
|
||||
args: {
|
||||
studentIds: [@student1.id.to_s, @student2.id.to_s],
|
||||
}
|
||||
)
|
||||
).to eq [@student2a1_submission]
|
||||
end
|
||||
|
||||
it "takes sorting criteria" do
|
||||
expect(
|
||||
course_type.submissionsConnection(
|
||||
current_user: @teacher,
|
||||
args: {
|
||||
studentIds: [@student1.id.to_s, @student2.id.to_s],
|
||||
orderBy: [{field: "graded_at", direction: "desc"}],
|
||||
}
|
||||
)
|
||||
).to eq [
|
||||
@student1a2_submission,
|
||||
@student2a1_submission,
|
||||
@student1a1_submission,
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../helpers/graphql_type_tester')
|
||||
|
||||
describe Types::SubmissionType do
|
||||
let(:submission) { Submission.new(score: 5) }
|
||||
let(:submission_type) { GraphQLTypeTester.new(Types::SubmissionType, submission) }
|
||||
|
||||
it "works" do
|
||||
expect(submission_type.score).to eq submission.score
|
||||
expect(submission_type.excused).to eq false
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue