Skip to content

Commit

Permalink
Update graphql to return module info for each alignment
Browse files Browse the repository at this point in the history
closes OUT-5104
flag=outcome_alignment_summary

Test plan:
- Enable Improved Outcomes Management FF
- Enable Outcome Alignment Summary FF
- Go to Course > Outcomes
- Create outcome and rubric and align them
- Create assignment and align with rubric
- Create classic quiz and align with rubric
- Create graded discussion and align with rubric
- Create two modules - "Module 1" and "Module 2";
publish "Module 1" then align assignment and quiz to it;
keep "Module 2" unpublushed and align discussion to it
- Open devtools -> network tab and reload the outcomes page;
lookup outcome id in the graphql response and write it down;
also write down the course id (obtain from URL)
- Open in browser canvas.docker/graphiql
- Execute query below replacing outcome and context id
query MyQuery {
  learningOutcome(id: "1") {
    alignments(contextId: "1", contextType: "Course") {
      title
      moduleName
      moduleUrl
      moduleWorkflowState
    }
  }
}
- Verify that query returns 4 alignments - to rubric,
quiz, assignment and discussion
- Verify that for rubric alignment, module_url, module_name
and module_workflw_state are null
- Verify that for assignment and quiz alignments, module
name is "Module 1" and module workflow state is "active"
- Verify that for discussion alignment, module name
is "Module 2" and module workflow state is "unpublished"
- Verify that module urls for assignment and quiz
alignments are the same
- Copy module_url from quiz alignment, open new browser tab,
type canvas.docker, append module url and press Enter
- Verify that the course module page loads
- Copy module_url from discussion alignment, open new browser
tab, type canvas.docker, append the module url and press Enter
- Verify that the course module page loads

Change-Id: Icca3339f45170181d55c0e259ed78d73bc3b1cf9
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/292252
Tested-by: Service Cloud Jenkins <[email protected]>
QA-Review: Angela Gomba <[email protected]>
Product-Review: Kyle Rosenbaum <[email protected]>
Reviewed-by: Chrystal Langston <[email protected]>
Reviewed-by: Marcus Pompeu <[email protected]>
  • Loading branch information
instout authored and Kyle Rosenbaum committed Jun 22, 2022
1 parent a80415e commit e86db0f
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 64 deletions.
46 changes: 35 additions & 11 deletions app/graphql/loaders/outcome_alignment_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,41 @@ def perform(outcomes)
end

outcomes.each do |outcome|
alignments = outcome.alignments.active.where(context: @context).order("title ASC").to_a
alignments.each do |alignment|
alignment.url = [
"/#{alignment.context_type.downcase.pluralize}",
alignment.context_id,
"outcomes",
alignment.learning_outcome_id,
"alignments",
alignment.id
].join("/")
end
# map assignment id to quiz and discussion ids
assignments_sub = Assignment
.active
.select("assignments.id as assignment_id, discussion_topics.id as discussion_id, quizzes.id as quizzes_id")
.where(context: @context)
.left_joins(:discussion_topic)
.left_joins(:quiz)
.to_sql

# map assignment id to module id
modules_sub = ContextModule
.not_deleted
.select("context_modules.id as module_id, context_modules.name as module_name, context_modules.workflow_state as module_workflow_state, content_tags.content_id as assignment_content_id, content_tags.content_type as assignment_content_type")
.where(context: @context)
.left_joins(:content_tags)
.to_sql

# map alignment id to assignment, quiz and discussion ids
alignments_sub = outcome.alignments
.select("content_tags.id, content_tags.content_id, content_tags.content_type, content_tags.context_id, content_tags.context_type, content_tags.title, content_tags.learning_outcome_id, content_tags.created_at, content_tags.updated_at, sub.assignment_id, sub.discussion_id, sub.quizzes_id")
.where(context: @context)
.joins("LEFT JOIN (#{assignments_sub}) sub ON content_tags.content_id = sub.assignment_id AND content_tags.content_type = 'Assignment'")
.to_sql

alignments = ContentTag
.select("sub1.*, sub2.module_id, sub2.module_name, sub2.module_workflow_state")
.from("(#{alignments_sub}) sub1")
.joins("LEFT JOIN (#{modules_sub}) sub2
ON (sub1.quizzes_id = sub2.assignment_content_id AND sub2.assignment_content_type = 'Quizzes::Quiz')
OR (sub1.discussion_id = sub2.assignment_content_id AND sub2.assignment_content_type = 'DiscussionTopic')
OR (sub1.assignment_id = sub2.assignment_content_id AND sub2.assignment_content_type = 'Assignment')
")
.order("title ASC")
.to_a

fulfill(outcome, alignments)
end
end
Expand Down
21 changes: 17 additions & 4 deletions app/graphql/types/outcome_alignment_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,35 @@

module Types
class OutcomeAlignmentType < ApplicationObjectType
graphql_name "OutcomeAlignmentType"
graphql_name "OutcomeAlignment"

implements GraphQL::Types::Relay::Node
implements Interfaces::TimestampInterface
implements Interfaces::LegacyIDInterface

global_id_field :id

field :id, ID, null: false
field :title, String, null: false
field :content_id, ID, null: false
field :content_type, String, null: false
field :context_id, ID, null: false
field :context_type, String, null: false
field :learning_outcome_id, ID, null: false
field :workflow_state, String, null: false
field :url, String, null: false
def url
[base_context_url.to_s, "outcomes", object.learning_outcome_id, "alignments", object.id].join("/")
end
field :module_id, String, null: true
field :module_name, String, null: true
field :module_url, String, null: true
def module_url
[base_context_url.to_s, "modules", object.module_id].join("/") if object.module_id
end
field :module_workflow_state, String, null: true

private

def base_context_url
["/#{object.context_type.downcase.pluralize}", object.context_id].join("/")
end
end
end
108 changes: 70 additions & 38 deletions spec/graphql/loaders/outcome_alignment_loader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,30 @@
#

describe Loaders::OutcomeAlignmentLoader do
before do
before :once do
course_model
@assignment1 = assignment_model({
course: @course,
name: "Assignment 1",
due_at: nil,
points_possible: 10,
submission_types: "online_text_entry,online_upload",
})
@assignment2 = assignment_model({
course: @course,
name: "Assignment 2",
due_at: nil,
points_possible: 20,
submission_types: "online_text_entry",
})
@outcome = outcome_model(context: @course, title: "outcome")
@alignment1 = @outcome.align(@assignment1, @course)
@alignment2 = @outcome.align(@assignment2, @course)
outcome_with_rubric
# create assignment, discussion assignment and quiz assignment
@quiz_item = assignment_quiz([], course: @course, title: "quiz item")
@quiz_assignment = @assignment
@assignment = @course.assignments.create!(title: "regular assignment")
@discussion_assignment = @course.assignments.create!(title: "discussion assignment")
@discussion_item = @course.discussion_topics.create!(
user: @teacher,
title: "discussion item",
assignment: @discussion_assignment
)
# create modules and assignments
@module1 = @course.context_modules.create!(name: "module1")
@module2 = @course.context_modules.create!(name: "module2", workflow_state: "unpublished")
@module1.add_item type: "assignment", id: @assignment.id
@module1.add_item type: "discussion_topic", id: @discussion_item.id
@module2.add_item type: "quiz", id: @quiz_item.id
# align rubric with different assignment types
@rubric.associate_with(@assignment, @course, purpose: "grading")
@rubric.associate_with(@discussion_assignment, @course, purpose: "grading")
@rubric.associate_with(@quiz_assignment, @course, purpose: "grading")

@course.account.enable_feature!(:outcome_alignment_summary)
end

Expand Down Expand Up @@ -73,31 +78,58 @@
end
end

it "resolves alignments properly" do
it "resolves to correct number of alignments" do
GraphQL::Batch.batch do
Loaders::OutcomeAlignmentLoader.for(
@course.id, "Course"
).load(@outcome).then do |alignment|
expect(alignment.is_a?(Array)).to be_truthy
expect(alignment.length).to eq 2
).load(@outcome).then do |alignments|
expect(alignments.is_a?(Array)).to be_truthy
expect(alignments.length).to eq 4
end
end
end

expect(alignment[0].id).to eq @alignment1.id
expect(alignment[0].content_type).to eq "Assignment"
expect(alignment[0].content_id).to eq @assignment1.id
expect(alignment[0].context_type).to eq "Course"
expect(alignment[0].context_id).to eq @course.id
expect(alignment[0].learning_outcome_id).to eq @outcome.id
expect(alignment[0].title).to eq @assignment1.title
expect(alignment[0].url).to eq "/courses/#{@course.id}/outcomes/#{@outcome.id}/alignments/#{@alignment1.id}"
it "resolves outcome alignments to assignment, rubric, quiz and discussion" do
GraphQL::Batch.batch do
Loaders::OutcomeAlignmentLoader.for(
@course.id, "Course"
).load(@outcome).then do |alignments|
alignments.each do |alignment|
if alignment.content_type == "Assignment"
content_id = @assignment.id
content_type = "Assignment"
module_id = @module1.id
module_name = @module1.name
module_workflow_state = "active"
title = @assignment.title
if alignment.title == "discussion item"
content_id = @discussion_assignment.id
title = @discussion_item.title
end
if alignment.title == "quiz item"
content_id = @quiz_assignment.id
module_id = @module2.id
module_name = @module2.name
module_workflow_state = "unpublished"
title = @quiz_item.title
end
else
content_id = @rubric.id
content_type = "Rubric"
title = @rubric.title
end

expect(alignment[1].id).to eq @alignment2.id
expect(alignment[1].content_type).to eq "Assignment"
expect(alignment[1].content_id).to eq @assignment2.id
expect(alignment[1].context_type).to eq "Course"
expect(alignment[1].context_id).to eq @course.id
expect(alignment[1].learning_outcome_id).to eq @outcome.id
expect(alignment[1].title).to eq @assignment2.title
expect(alignment[1].url).to eq "/courses/#{@course.id}/outcomes/#{@outcome.id}/alignments/#{@alignment2.id}"
expect(alignment.id).not_to be_nil
expect(alignment.content_id).to eq content_id
expect(alignment.content_type).to eq content_type
expect(alignment.context_id).to eq @course.id
expect(alignment.context_type).to eq "Course"
expect(alignment.title).to eq title
expect(alignment.learning_outcome_id).to eq @outcome.id
expect(alignment.module_id).to eq module_id
expect(alignment.module_name).to eq module_name
expect(alignment.module_workflow_state).to eq module_workflow_state
end
end
end
end
Expand Down
43 changes: 32 additions & 11 deletions spec/graphql/types/outcome_alignment_type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,65 +34,86 @@
})
@outcome = outcome_model(context: @course, title: "outcome")
@outcome_alignment = @outcome.align(@assignment, @course)
@module = @course.context_modules.create!(name: "module", workflow_state: "unpublished")
@module.add_item type: "assignment", id: @assignment.id

@course.account.enable_feature!(:outcome_alignment_summary)
end

let(:graphql_context) { { current_user: @admin } }
let(:outcome_type) { GraphQLTypeTester.new(@outcome, graphql_context) }

describe "returns correct values for alignment fields" do
it "_id" do
it "returns _id" do
expect(
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { _id }")[0]
).to eq @outcome_alignment.id.to_s
end

it "learning_outcome_id" do
it "returns learning_outcome_id" do
expect(
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { learningOutcomeId }")[0]
).to eq @outcome.id.to_s
end

it "context_id" do
it "returns context_id" do
expect(
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { contextId }")[0]
).to eq @course.id.to_s
end

it "context_type" do
it "returns context_type" do
expect(
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { contextType }")[0]
).to eq "Course"
end

it "content_id" do
it "returns content_id" do
expect(
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { contentId }")[0]
).to eq @outcome_alignment.content_id.to_s
end

it "content_type" do
it "returns content_type" do
expect(
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { contentType }")[0]
).to eq "Assignment"
end

it "title" do
it "returns title" do
expect(
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { title }")[0]
).to eq @assignment.title
end

it "url" do
it "returns url" do
expect(
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { url }")[0]
).to eq "/courses/#{@course.id}/outcomes/#{@outcome.id}/alignments/#{@outcome_alignment.id}"
end

it "workflowState" do
it "returns module_id" do
expect(
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { moduleId }")[0]
).to eq @module.id.to_s
end

it "returns module_name" do
expect(
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { moduleName }")[0]
).to eq @module.name
end

it "returns module_url" do
expect(
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { moduleUrl }")[0]
).to eq "/courses/#{@course.id}/modules/#{@module.id}"
end

it "returns module_workflow_state" do
expect(
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { workflowState }")[0]
).to eq "active"
outcome_type.resolve("alignments(contextType: \"Course\", contextId: #{@course.id}) { moduleWorkflowState }")[0]
).to eq @module.workflow_state
end
end
end

0 comments on commit e86db0f

Please sign in to comment.