Add queries for getting total subgroups and outcomes
Updates LearningOutcomeGroupType to return the number of nested subgroups and nested outcomes for a learning outcome group. closes OUT-4022 flag=improved_outcomes_management Test plan: - Create nested learning outcome groups - For each nested learning outcome group create learning outcomes - Make a query in /graphiql for an accout or a course and retrieve the parent learning outcome group. e.g. ``` query MyQuery { account(id: 1) { rootOutcomeGroup { id _id title childGroupsCount outcomesCount } } } ``` - Compare the values at the fields childGroupsCount and outcomesCount with the amount of nested subgroups and nested outcomes respectively so they should match Change-Id: Ie63a5b134d661832f5a22714b693dddc6887cec3 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/254149 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Pat Renner <prenner@instructure.com> Reviewed-by: Michael Brewer-Davis <mbd@instructure.com> QA-Review: Brian Watson <bwatson@instructure.com> Product-Review: Michael Brewer-Davis <mbd@instructure.com>
This commit is contained in:
parent
55c88f7d39
commit
16c7034ff7
|
@ -50,19 +50,23 @@ module Types
|
|||
|
||||
field :child_groups_count, Integer, null: false
|
||||
def child_groups_count
|
||||
# Not Implemented yet
|
||||
0
|
||||
learning_outcome_group_children_service.total_subgroups(object.id)
|
||||
end
|
||||
|
||||
field :outcomes_count, Integer, null: false
|
||||
def outcomes_count
|
||||
# Not Implemented yet
|
||||
0
|
||||
learning_outcome_group_children_service.total_outcomes(object.id)
|
||||
end
|
||||
|
||||
field :outcomes, Types::ContentTagConnection, null: false
|
||||
def outcomes
|
||||
object.child_outcome_links.active.order_by_outcome_title
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def learning_outcome_group_children_service
|
||||
@learning_outcome_group_children_service ||= Outcomes::LearningOutcomeGroupChildren.new(object.context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
# 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/>.
|
||||
#
|
||||
|
||||
module Outcomes
|
||||
class LearningOutcomeGroupChildren
|
||||
attr_reader :context
|
||||
|
||||
def initialize(context = nil)
|
||||
@context = context
|
||||
end
|
||||
|
||||
def total_subgroups(learning_outcome_group_id)
|
||||
children_ids(learning_outcome_group_id).length
|
||||
end
|
||||
|
||||
def total_outcomes(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).
|
||||
select(:content_id).
|
||||
distinct.
|
||||
count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def children_ids(learning_outcome_group_id)
|
||||
parent = data.find { |d| d['parent_id'] == learning_outcome_group_id }
|
||||
parent&.dig('descendant_ids')&.tr('{}', '')&.split(',') || []
|
||||
end
|
||||
|
||||
def data
|
||||
@data ||= begin
|
||||
LearningOutcomeGroup.connection.execute(<<-SQL).as_json
|
||||
WITH RECURSIVE levels AS (
|
||||
SELECT id, learning_outcome_group_id AS parent_id
|
||||
FROM (#{LearningOutcomeGroup.active.where(context: @context).to_sql}) AS data
|
||||
UNION ALL
|
||||
SELECT child.id AS id, parent.parent_id AS parent_id
|
||||
FROM #{LearningOutcomeGroup.quoted_table_name} child
|
||||
INNER JOIN levels parent ON parent.id = child.learning_outcome_group_id
|
||||
WHERE child.workflow_state <> 'deleted'
|
||||
)
|
||||
SELECT parent_id, array_agg(id) AS descendant_ids FROM levels WHERE parent_id IS NOT NULL GROUP BY parent_id
|
||||
SQL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -28,6 +28,7 @@ describe Types::LearningOutcomeGroupType do
|
|||
@account_user = Account.default.account_users.first
|
||||
@parent_group = outcome_group_model(context: Account.default)
|
||||
@child_group = outcome_group_model(context: Account.default)
|
||||
@child_group3 = outcome_group_model(context: Account.default)
|
||||
@child_group2 = outcome_group_model(context: Account.default, workflow_state: 'deleted')
|
||||
outcome_group_model(context: Account.default, vendor_guid: "vendor_guid")
|
||||
@outcome_group.learning_outcome_group = @parent_group
|
||||
|
@ -36,6 +37,8 @@ describe Types::LearningOutcomeGroupType do
|
|||
@child_group.save!
|
||||
@child_group2.learning_outcome_group = @outcome_group
|
||||
@child_group2.save
|
||||
@child_group3.learning_outcome_group = @outcome_group
|
||||
@child_group3.save!
|
||||
@user = @admin
|
||||
@outcome1 = outcome_model(context: Account.default, outcome_group: @outcome_group, short_description: "BBBB")
|
||||
@outcome2 = outcome_model(context: Account.default, outcome_group: @outcome_group, short_description: "AAAA")
|
||||
|
@ -54,7 +57,8 @@ describe Types::LearningOutcomeGroupType do
|
|||
expect(outcome_group_type.resolve("outcomesCount")).to be_a Integer
|
||||
expect(outcome_group_type.resolve("parentOutcomeGroup { _id }")).to eq @parent_group.id.to_s
|
||||
expect(outcome_group_type.resolve("canEdit")).to eq true
|
||||
expect(outcome_group_type.resolve("childGroups { nodes { _id } }")).to match_array([@child_group.id.to_s])
|
||||
expect(outcome_group_type.resolve("childGroups { nodes { _id } }")).
|
||||
to match_array([@child_group.id.to_s, @child_group3.id.to_s])
|
||||
end
|
||||
|
||||
it "gets outcomes ordered by title" do
|
||||
|
@ -105,4 +109,16 @@ describe Types::LearningOutcomeGroupType do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#child_groups_count' do
|
||||
it 'returns the total nested outcome groups' do
|
||||
expect(outcome_group_type.resolve("childGroupsCount")).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
describe '#outcomes_count' do
|
||||
it 'returns the total outcomes at the nested outcome groups' do
|
||||
expect(outcome_group_type.resolve("outcomesCount")).to eq 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
# 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 'spec_helper'
|
||||
|
||||
describe Outcomes::LearningOutcomeGroupChildren do
|
||||
subject { described_class.new(context) }
|
||||
|
||||
# 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!(: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) }
|
||||
# rubocop:enable RSpec/LetSetup
|
||||
|
||||
|
||||
before do
|
||||
Rails.cache.clear
|
||||
end
|
||||
|
||||
describe '#total_subgroups' do
|
||||
it 'returns the total sugroups for a learning outcome group' do
|
||||
expect(subject.total_subgroups(g0.id)).to eq 6
|
||||
expect(subject.total_subgroups(g1.id)).to eq 3
|
||||
expect(subject.total_subgroups(g2.id)).to eq 1
|
||||
expect(subject.total_subgroups(g3.id)).to eq 1
|
||||
expect(subject.total_subgroups(g4.id)).to eq 0
|
||||
expect(subject.total_subgroups(g5.id)).to eq 0
|
||||
expect(subject.total_subgroups(g6.id)).to eq 0
|
||||
end
|
||||
|
||||
context 'when outcome group is deleted' do
|
||||
before { g4.update(workflow_state: 'deleted') }
|
||||
|
||||
it 'returns the total sugroups for a learning outcome group without the deleted groups' do
|
||||
expect(subject.total_subgroups(g0.id)).to eq 5
|
||||
expect(subject.total_subgroups(g1.id)).to eq 2
|
||||
expect(subject.total_subgroups(g2.id)).to eq 1
|
||||
expect(subject.total_subgroups(g3.id)).to eq 1
|
||||
expect(subject.total_subgroups(g4.id)).to eq 0
|
||||
expect(subject.total_subgroups(g5.id)).to eq 0
|
||||
expect(subject.total_subgroups(g6.id)).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'when context is nil' do
|
||||
subject { described_class.new(nil) }
|
||||
|
||||
it 'returns global outcome groups' do
|
||||
expect(subject.total_subgroups(global_group.id)).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#total_outcomes' do
|
||||
it 'returns the total nested outcomes at each group' do
|
||||
expect(subject.total_outcomes(g0.id)).to eq 12
|
||||
expect(subject.total_outcomes(g1.id)).to eq 9
|
||||
expect(subject.total_outcomes(g2.id)).to eq 2
|
||||
expect(subject.total_outcomes(g3.id)).to eq 6
|
||||
expect(subject.total_outcomes(g4.id)).to eq 1
|
||||
expect(subject.total_outcomes(g5.id)).to eq 1
|
||||
expect(subject.total_outcomes(g6.id)).to eq 3
|
||||
end
|
||||
|
||||
context 'when outcome is deleted' do
|
||||
before { o4.destroy }
|
||||
|
||||
it 'returns the total sugroups for a learning outcome group without the deleted groups' do
|
||||
expect(subject.total_outcomes(g0.id)).to eq 11
|
||||
expect(subject.total_outcomes(g1.id)).to eq 8
|
||||
expect(subject.total_outcomes(g2.id)).to eq 2
|
||||
expect(subject.total_outcomes(g3.id)).to eq 5
|
||||
expect(subject.total_outcomes(g4.id)).to eq 1
|
||||
expect(subject.total_outcomes(g5.id)).to eq 1
|
||||
expect(subject.total_outcomes(g6.id)).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
context 'when context is nil' do
|
||||
subject { described_class.new(nil) }
|
||||
|
||||
it 'returns global outcomes' do
|
||||
expect(subject.total_outcomes(global_group.id)).to eq 2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue