Fetch suboutcomes within a learning outcome group via graphql

Add ability to fetch all sub-outcomes of a learning outcome group
Ordered by parent group title then outcome short description. The
Results should be paginated to accommodate for infinite scroll.

closes OUT-4130
flag=improved_outcomes_management

test plan
- PreReq: Account/Course with outcomes and groups
- Log into Canvas and navigate to graphiql
- Run the below query
- outcomes should be populated with all outcomes and suboutcomes
For the root outcome group order by group title, outcome short
description
- Run the below query
query GroupDetailQuery($id: ID!, $outcomesCursor: String) {
    group: legacyNode(type: LearningOutcomeGroup, _id: $id) {
      ... on LearningOutcomeGroup {
        _id
        description
        title
        outcomesCount
        outcomes(first: 10, after: $outcomesCursor) {
          pageInfo {
            hasNextPage
            startCursor
            endCursor
          }
          nodes {
            ... on LearningOutcome {
              _id
              description
              title
            }
          }
        }
      }
    }
  }
{
  "id": 1,
  "outcomesCursor": ""
}
- All outcomes should be returned ordered by parent group name,
then outcome name
- Change the name of an outcome and/or group to force it to change
the order.
- Run the query again, and you should see the updated outcome or
Parent group’s outcomes first before the remain outcomes

Change-Id: Icdad82c0b57ca625924d9b0af7d7b3304b88c251
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/257653
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Product-Review: Michael Brewer-Davis <mbd@instructure.com>
Reviewed-by: Pat Renner <prenner@instructure.com>
Reviewed-by: Michael Brewer-Davis <mbd@instructure.com>
QA-Review: Pat Renner <prenner@instructure.com>
This commit is contained in:
Chrystal Langston 2021-01-28 11:31:25 -05:00
parent d2b6ebb112
commit 7f1f432987
3 changed files with 132 additions and 22 deletions

View File

@ -60,7 +60,7 @@ module Types
field :outcomes, Types::ContentTagConnection, null: false
def outcomes
object.child_outcome_links.active.order_by_outcome_title
learning_outcome_group_children_service.suboutcomes_by_group_id(object.id)
end
private

View File

@ -19,7 +19,7 @@
#
module Outcomes
class LearningOutcomeGroupChildren
class LearningOutcomeGroupChildren
attr_reader :context
def initialize(context = nil)
@ -41,6 +41,18 @@ module Outcomes
count
end
def suboutcomes_by_group_id(learning_outcome_group_id)
ids = children_ids(learning_outcome_group_id) << learning_outcome_group_id
ContentTag.active.learning_outcome_links.
where(associated_asset_id: ids).
joins(:learning_outcome_content).
joins("INNER JOIN #{LearningOutcomeGroup.quoted_table_name} AS logs
on logs.id = content_tags.associated_asset_id").
order(LearningOutcomeGroup.best_unicode_collation_key('logs.title'),
LearningOutcome.best_unicode_collation_key('short_description'))
end
private
def children_ids(learning_outcome_group_id)

View File

@ -26,29 +26,55 @@ describe Outcomes::LearningOutcomeGroupChildren do
# rubocop:disable RSpec/LetSetup
let!(:context) { Account.default }
let!(:global_group) { LearningOutcomeGroup.create(title: 'global') }
let!(:global_outcome1) { outcome_model(outcome_group: global_group) }
let!(:global_outcome2) { outcome_model(outcome_group: global_group) }
let!(:global_outcome1) { outcome_model(outcome_group: global_group, title: 'G Outcome 1') }
let!(:global_outcome2) { outcome_model(outcome_group: global_group, title: 'G Outcome 2') }
let!(:g0) { context.root_outcome_group }
let!(:g1) { outcome_group_model(context: context, outcome_group_id: g0) }
let!(:g2) { outcome_group_model(context: context, outcome_group_id: g0) }
let!(:g3) { outcome_group_model(context: context, outcome_group_id: g1) }
let!(:g4) { outcome_group_model(context: context, outcome_group_id: g1) }
let!(:g5) { outcome_group_model(context: context, outcome_group_id: g2) }
let!(:g6) { outcome_group_model(context: context, outcome_group_id: g3) }
let!(:o0) { outcome_model(context: context, outcome_group: g0) }
let!(:o1) { outcome_model(context: context, outcome_group: g1) }
let!(:o2) { outcome_model(context: context, outcome_group: g1) }
let!(:o3) { outcome_model(context: context, outcome_group: g2) }
let!(:o4) { outcome_model(context: context, outcome_group: g3) }
let!(:o5) { outcome_model(context: context, outcome_group: g3) }
let!(:o6) { outcome_model(context: context, outcome_group: g3) }
let!(:o7) { outcome_model(context: context, outcome_group: g4) }
let!(:o8) { outcome_model(context: context, outcome_group: g5) }
let!(:o9) { outcome_model(context: context, outcome_group: g6) }
let!(:o10) { outcome_model(context: context, outcome_group: g6) }
let!(:o11) { outcome_model(context: context, outcome_group: g6) }
let!(:g1) { outcome_group_model(context: context, outcome_group_id: g0, title: 'Group 1.1') }
let!(:g2) { outcome_group_model(context: context, outcome_group_id: g0, title: 'Group 1.2') }
let!(:g3) { outcome_group_model(context: context, outcome_group_id: g1, title: 'Group 2.1') }
let!(:g4) { outcome_group_model(context: context, outcome_group_id: g1, title: 'Group 2.2') }
let!(:g5) { outcome_group_model(context: context, outcome_group_id: g2, title: 'Group 3') }
let!(:g6) { outcome_group_model(context: context, outcome_group_id: g3, title: 'Group 4') }
let!(:o0) { outcome_model(context: context, outcome_group: g0, title:'Outcome 1', short_description: 'Outcome 1') }
let!(:o1) { outcome_model(context: context, outcome_group: g1, title:'Outcome 2.1', short_description: 'Outcome 2.1') }
let!(:o2) { outcome_model(context: context, outcome_group: g1, title:'Outcome 2.2', short_description: 'Outcome 2.2') }
let!(:o3) { outcome_model(context: context, outcome_group: g2, title:'Outcome 3', short_description: 'Outcome 3') }
let!(:o4) { outcome_model(context: context, outcome_group: g3, title:'Outcome 4.1', short_description: 'Outcome 4.1') }
let!(:o5) { outcome_model(context: context, outcome_group: g3, title:'Outcome 4.2', short_description: 'Outcome 4.2') }
let!(:o6) { outcome_model(context: context, outcome_group: g3, title:'Outcome 4.3', short_description: 'Outcome 4.3') }
let!(:o7) { outcome_model(context: context, outcome_group: g4, title:'Outcome 5', short_description: 'Outcome 5') }
let!(:o8) { outcome_model(context: context, outcome_group: g5, title:'Outcome 6', short_description: 'Outcome 6') }
let!(:o9) { outcome_model(context: context, outcome_group: g6, title:'Outcome 7.1', short_description: 'Outcome 7.1') }
let!(:o10) { outcome_model(context: context, outcome_group: g6, title:'Outcome 7.2', short_description: 'Outcome 7.2') }
let!(:o11) { outcome_model(context: context, outcome_group: g6, title:'Outcome 7.3', short_description: 'Outcome 7.3') }
# rubocop:enable RSpec/LetSetup
# Outcome Structure for visual reference
# Global
# global_group: global
# global_outcome1: G Outcome 1
# global_outcome2: G Outcome 2
# Root
# g0: Root/Content Name
# o0: Outcome 1
# g1: Group 1.1
# o1: Outcome 2.1
# o2: Outcome 2.2
# g3: Group 2.1
# o4: Outcome 4.1
# o5: Outcome 4.2
# o6: outcome 4.3
# g6: Group 4
# o9: Outcome 7.1
# o10: Outcome 7.2
# o11: Outcome 7.3
# g4: Group 2.2
# o7: Outcome 5
# g2: Group 1.2
# o3: Outcome 2
# g5: Group 3
# o8: Outcome 6
before do
Rails.cache.clear
@ -121,4 +147,76 @@ describe Outcomes::LearningOutcomeGroupChildren do
end
end
end
describe '#suboutcomes_by_group_id' do
it 'returns the outcomes ordered by parent group title then outcome short_description' do
g_outcomes = subject.suboutcomes_by_group_id(global_group.id).map(&:learning_outcome_content).map(&:short_description)
expect(g_outcomes).to match_array(['G Outcome 1', 'G Outcome 2'])
r_outcomes = subject.suboutcomes_by_group_id(g0.id).map(&:learning_outcome_content).map(&:short_description)
expect(r_outcomes).to match_array(
[
'Outcome 1', 'Outcome 2.1', 'Outcome 2.2', 'Outcome 3', 'Outcome 4.1',
'Outcome 4.2', 'Outcome 4.3', 'Outcome 5', 'Outcome 6', 'Outcome 7.1',
'Outcome 7.2', 'Outcome 7.3'
]
)
end
context 'when g2 title is updated with a letter that will proceed others' do
before {g2.update!(title: 'A Group 3')}
it 'should return the g2s outcome (o3) first' do
outcomes = subject.suboutcomes_by_group_id(g0.id).map(&:learning_outcome_content).map(&:short_description)
expect(outcomes).to match_array(
[
'Outcome 3', 'Outcome 1', 'Outcome 2.1', 'Outcome 2.2', 'Outcome 4.1',
'Outcome 4.2', 'Outcome 4.3', 'Outcome 5', 'Outcome 6', 'Outcome 7.1',
'Outcome 7.2', 'Outcome 7.3'
]
)
end
end
context 'when o5 short_description is updated with a letter that will proceed others' do
# NOTE: when you update the short_description of a LearningOutcome it does NOT update the
# content tag title.
before {o5.update!(short_description: 'A Outcome 4.2')}
it 'o5 should be returned before o4 but not o2 and o3' do
outcomes = subject.suboutcomes_by_group_id(g1.id).map(&:learning_outcome_content).map(&:short_description)
expect(outcomes).to match_array(
[
'Outcome 2.1', 'Outcome 2.2', 'A Outcome 4.2', 'Outcome 4.1', 'Outcome 4.3',
'Outcome 5', 'Outcome 7.1', 'Outcome 7.2', 'Outcome 7.3'
]
)
end
end
context 'when g4 title and o6 short_description is updated with a letter that will proceed others' do
before {
g4.update!(title: 'A Group 2.2')
o6.update!(short_description: 'A Outcome 4.3')
}
it 'should return the g4s outcomes first and o6 should be first before other Outcomes 4.x' do
outcomes = subject.suboutcomes_by_group_id(g1.id).map(&:learning_outcome_content).map(&:short_description)
expect(outcomes).to match_array(
[
'Outcome 5', 'Outcome 2.1', 'Outcome 2.2', 'A Outcome 4.3', 'Outcome 4.1',
'Outcome 4.2', 'Outcome 7.1', 'Outcome 7.2', 'Outcome 7.3'
]
)
end
end
context 'when context is nil' do
subject { described_class.new(nil) }
it 'returns global outcomes' do
outcomes = subject.suboutcomes_by_group_id(global_group.id).map(&:learning_outcome_content).map(&:short_description)
expect(outcomes).to match_array(['G Outcome 1', 'G Outcome 2'])
end
end
end
end