Resolve outcome friendly descriptions via the account hierarchy

closes OUT-4402
flag=outcomes_friendly_description

Test plan:
  - Create 2 outcomes (outcome 1 and 2) in a course
    Make sure you have those 2 outcomes inside the same group
    to make the test easier
  - Create an outcome friendly description for:
    outcome 1: for the course and for the account
    outcome 2: just for the account
    can create using the snippet in console
    OutcomeFriendlyDescription.create!({
      learning_outcome: outcome,
      context: course || account,
      description: "account or course description"
    })
  - Open graphiql and the queries (replace THE_GROUP_ID,
    THE_COURSE_ID and THE_ACCOUNT_ID):
    {
      legacyNode(type: LearningOutcomeGroup, _id: THE_GROUP_ID) {
        ... on LearningOutcomeGroup {
          outcomes(first: 2) {
            nodes {
              ... on LearningOutcome {
                _id
                title
                friendlyDescription(
                  contextId: THE_COURSE_ID
                  contextType: "Course"
                ) {
                  _id
                  description
                }
              }
            }
          }
        }
      }
    }
    Make sure you see an account friendlyDescription and a course
    friendlyDescription

    {
      legacyNode(type: LearningOutcomeGroup, _id: THE_GROUP_ID) {
        ... on LearningOutcomeGroup {
          outcomes(first: 2) {
            nodes {
              ... on LearningOutcome {
                _id
                title
                friendlyDescription(
                  contextId: THE_ACCOUNT_ID
                  contextType: "Account"
                ) {
                  _id
                  description
                }
              }
            }
          }
        }
      }
    }
    Make sure you see only account friendlyDescriptions for both outcomes

Change-Id: I809422d8762c961ec4e5586fce645e9911ca22ff
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/267852
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Augusto Callejas <acallejas@instructure.com>
QA-Review: Martin Yosifov <martin.yosifov@instructure.com>
Product-Review: Augusto Callejas <acallejas@instructure.com>
This commit is contained in:
Manoel Quirino Neto 2021-06-25 10:43:47 -03:00 committed by Manoel Quirino
parent d43feb92d4
commit 8675815425
3 changed files with 238 additions and 7 deletions

View File

@ -19,22 +19,72 @@
#
class Loaders::OutcomeFriendlyDescriptionLoader < GraphQL::Batch::Loader
VALID_CONTEXT_TYPES = ['Course', 'Account'].freeze
def initialize(context_id, context_type)
@context_id = context_id
@context_type = context_type
end
def perform(outcome_ids)
OutcomeFriendlyDescription.active.where(
learning_outcome_id: outcome_ids,
context_id: @context_id,
context_type: @context_type,
).each do |friendly_description|
fulfill(friendly_description.learning_outcome_id, friendly_description)
def context_queries
queries = []
if @course
queries << OutcomeFriendlyDescription.sanitize_sql([
"context_type = 'Course' AND context_id = ?", @course.id
])
end
queries << OutcomeFriendlyDescription.sanitize_sql([
"context_type = 'Account' AND context_id IN (?)", @account.account_chain_ids
])
'(' + queries.join(') OR (') + ')'
end
def valid_context?
return false unless VALID_CONTEXT_TYPES.include?(@context_type)
@context = @context_type.constantize.active.find_by(id: @context_id)
return false unless @context
if @context.is_a?(Course)
@course = @context
@account = @context.account
else
@account = @context
end
true
end
def nullify_resting(outcome_ids)
outcome_ids.each do |outcome_id|
fulfill(outcome_id, nil) unless fulfilled?(outcome_id)
end
end
def perform(outcome_ids)
unless valid_context?
nullify_resting(outcome_ids)
return
end
account_order = @account.account_chain_ids
# get all friendly description for the course and all parent accounts once
# sort by course, then the account parent order in account_chain_ids
# and fulfill every friendly description
friendly_descriptions = OutcomeFriendlyDescription.active.where(
learning_outcome_id: outcome_ids
).where(context_queries).to_a.sort_by do |friendly_description|
friendly_description.context_type == 'Course' ? 0 : account_order.index(friendly_description.context_id) + 1
end
friendly_descriptions.each do |friendly_description|
outcome_id = friendly_description.learning_outcome_id
fulfill(outcome_id, friendly_description) unless fulfilled?(outcome_id)
end
nullify_resting(outcome_ids)
end
end

View File

@ -0,0 +1,166 @@
# frozen_string_literal: true
#
# Copyright (C) 2021 - 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')
describe Loaders::OutcomeFriendlyDescriptionLoader do
before do
course_with_student(active_all: true)
outcome_model(context: @course)
@course_account = @course.account
@parent_account = account_model
@course_account.parent_account = @parent_account
@course_account.save!
end
def create_course_fd
@course_fd = OutcomeFriendlyDescription.create!({
learning_outcome: @outcome,
context: @course,
description: "course's description"
})
end
def create_account_fd
@account_fd = OutcomeFriendlyDescription.create!({
learning_outcome: @outcome,
context: @course_account,
description: "account's description"
})
end
def create_parent_account_fd
@parent_account_fd = OutcomeFriendlyDescription.create!({
learning_outcome: @outcome,
context: @parent_account,
description: "parent account's description"
})
end
it "prioritizes course fd" do
create_course_fd
create_account_fd
create_parent_account_fd
GraphQL::Batch.batch do
fd_loader = Loaders::OutcomeFriendlyDescriptionLoader.for(
@course.id, 'Course'
)
fd_loader.load(@outcome.id).then { |fd|
expect(fd).to eq @course_fd
}
end
end
it "ignores course fd if we're loading for the account" do
create_course_fd
create_account_fd
create_parent_account_fd
GraphQL::Batch.batch do
fd_loader = Loaders::OutcomeFriendlyDescriptionLoader.for(
@course_account.id, 'Account'
)
fd_loader.load(@outcome.id).then { |fd|
expect(fd).to eq @account_fd
}
end
end
it "resolves account fd when there isn't any course fd" do
create_account_fd
GraphQL::Batch.batch do
fd_loader = Loaders::OutcomeFriendlyDescriptionLoader.for(
@course.id, 'Course'
)
fd_loader.load(@outcome.id).then { |fd|
expect(fd).to eq @account_fd
}
end
end
it "resolves to parent account fd when there isn't any course and account fd" do
create_parent_account_fd
GraphQL::Batch.batch do
fd_loader = Loaders::OutcomeFriendlyDescriptionLoader.for(
@course.id, 'Course'
)
fd_loader.load(@outcome.id).then { |fd|
expect(fd).to eq @parent_account_fd
}
end
end
it "resolves to parent account fd when there isn't any account fd" do
create_parent_account_fd
GraphQL::Batch.batch do
fd_loader = Loaders::OutcomeFriendlyDescriptionLoader.for(
@account.id, 'Account'
)
fd_loader.load(@outcome.id).then { |fd|
expect(fd).to eq @parent_account_fd
}
end
end
it "resolves to nil when there is no fd associated with the outcome" do
create_course_fd
create_account_fd
create_parent_account_fd
GraphQL::Batch.batch do
fd_loader = Loaders::OutcomeFriendlyDescriptionLoader.for(
@course.id, 'Course'
)
fd_loader.load(@outcome.id + 1).then { |fd|
expect(fd).to be_nil
}
end
end
it "resolves to nil when passsing invalid context type" do
create_course_fd
GraphQL::Batch.batch do
fd_loader = Loaders::OutcomeFriendlyDescriptionLoader.for(
@course.id, 'InvalidContextType'
)
fd_loader.load(@outcome.id).then { |fd|
expect(fd).to be_nil
}
end
end
it "resolves to nil when passsing invalid context id" do
create_course_fd
GraphQL::Batch.batch do
fd_loader = Loaders::OutcomeFriendlyDescriptionLoader.for(
@course.id + 99, 'Course'
)
fd_loader.load(@outcome.id).then { |fd|
expect(fd).to be_nil
}
end
end
end

View File

@ -101,4 +101,19 @@ describe Types::LearningOutcomeType do
.to eq true
end
end
context "friendlyDescription" do
let(:course) { Course.create! }
it "resolves friendly description correctly" do
course_fd = OutcomeFriendlyDescription.create!({
learning_outcome: @outcome,
context: course,
description: "course's description"
})
expect(outcome_type.resolve("friendlyDescription(contextType: \"Course\", contextId: #{course.id}) { _id }"))
.to eq course_fd.id.to_s
end
end
end