add course and sub-account count to accounts API

closes FOO-4553
closes GH-2380
flag = none

test plan:
- Exercise the following API's
  - GET /api/v1/accounts
  - GET /api/v1/accounts/:account_id/sub_accounts
  - include[]=course_count,sub_account_count
- Verify that the response includes the `course_count` and
  `sub_account_count` fields for each account and that the counts
  reflect reality for active courses and sub-accounts that are
  direct descendants

Change-Id: I54bfe72e08fc695728d08a6c71ea04cd06a0b49f
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/351652
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Michael Hulse <michael.hulse@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: August Thornton <august@instructure.com>
This commit is contained in:
August Thornton 2024-07-01 19:26:14 -07:00
parent 43aacea1d2
commit 08f2e88a69
4 changed files with 67 additions and 4 deletions

View File

@ -87,6 +87,16 @@
# "example": "12",
# "type": "integer"
# },
# "course_count": {
# "description": "The number of courses directly under the account (available via include)",
# "example": "10",
# "type": "integer"
# },
# "sub_account_count": {
# "description": "The number of sub-accounts directly under the account (available via include)",
# "example": "10",
# "type": "integer"
# },
# "lti_guid": {
# "description": "The account's identifier that is sent as context_id in LTI launches.",
# "example": "123xyz",
@ -314,12 +324,14 @@ class AccountsController < ApplicationController
# Typically, students and even teachers will get an empty list in response,
# only account admins can view the accounts that they are in.
#
# @argument include[] [String, "lti_guid"|"registration_settings"|"services"]
# @argument include[] [String, "lti_guid"|"registration_settings"|"services"|"course_count"|"sub_account_count"]
# Array of additional information to include.
#
# "lti_guid":: the 'tool_consumer_instance_guid' that will be sent for this account on LTI launches
# "registration_settings":: returns info about the privacy policy and terms of use
# "services":: returns services and whether they are enabled (requires account management permissions)
# "course_count":: returns the number of courses directly under each account
# "sub_account_count":: returns the number of sub-accounts directly under each account
#
# @returns [Account]
def index
@ -538,6 +550,12 @@ class AccountsController < ApplicationController
# this account will be returned (though still paginated). If false, only
# direct sub-accounts of this account will be returned. Defaults to false.
#
# @argument include[] [String, "course_count"|"sub_account_count"]
# Array of additional information to include.
#
# "course_count":: returns the number of courses directly under each account
# "sub_account_count":: returns the number of sub-accounts directly under each account
#
# @example_request
# curl https://<canvas>/api/v1/accounts/<account_id>/sub_accounts \
# -H 'Authorization: Bearer <token>'
@ -569,8 +587,11 @@ class AccountsController < ApplicationController
api_v1_sub_accounts_url,
total_entries: recursive ? nil : @accounts.count)
supported_includes = %w[course_count sub_account_count]
includes = (supported_includes.any? { |i| params[:include]&.include?(i) }) ? supported_includes : []
ActiveRecord::Associations.preload(@accounts, [:root_account, :parent_account])
render json: @accounts.map { |a| account_json(a, @current_user, session, []) }
render json: @accounts.map { |a| account_json(a, @current_user, session, includes) }
end
# @API Get the Terms of Service

View File

@ -1841,11 +1841,11 @@ class Account < ActiveRecord::Base
end
def course_count
courses.active.count
courses.active.size
end
def sub_account_count
sub_accounts.active.count
sub_accounts.active.size
end
def user_count

View File

@ -68,6 +68,8 @@ module Api::V1::Account
if includes.include?("services") && account.grants_right?(user, session, :manage_account_settings)
hash["services"] = Account.services_exposed_to_ui_hash(nil, user, account).keys.index_with { |k| account.service_enabled?(k) }
end
hash["course_count"] = account.course_count if includes.include?("course_count")
hash["sub_account_count"] = account.sub_account_count if includes.include?("sub_account_count")
hash["global_id"] = account.global_id if includes.include?("global_id")

View File

@ -195,6 +195,29 @@ describe "Accounts API", type: :request do
"Account 2"]
end
it "includes course count if requested" do
2.times { course_factory(active_all: true, account: @a1.sub_accounts.find_by(name: "subby")) }
json = api_call(:get,
"/api/v1/accounts/#{@a1.id}/sub_accounts?include[]=course_count",
{ controller: "accounts",
action: "sub_accounts",
account_id: @a1.id.to_s,
format: "json",
include: ["course_count"] })
expect(json.pluck("course_count")).to match_array([2, 0, 0, 0])
end
it "includes sub-account count if requested" do
json = api_call(:get,
"/api/v1/accounts/#{@a1.id}/sub_accounts?include[]=sub_account_count",
{ controller: "accounts",
action: "sub_accounts",
account_id: @a1.id.to_s,
format: "json",
include: ["sub_account_count"] })
expect(json.pluck("sub_account_count")).to match_array([0, 0, 3, 3])
end
it "adds sub account" do
previous_sub_count = @a1.sub_accounts.size
api_call(:post,
@ -332,6 +355,23 @@ describe "Accounts API", type: :request do
expect(json[0]["lti_guid"]).to eq "hey"
end
it "includes course count if requested" do
2.times { course_factory(active_all: true, account: @a1) }
json = api_call(:get,
"/api/v1/accounts?include[]=course_count",
{ controller: "accounts", action: "index", format: "json", include: ["course_count"] },
{})
expect(json.pluck("course_count")).to match_array([2, 0])
end
it "includes sub-account count if requested" do
json = api_call(:get,
"/api/v1/accounts?include[]=sub_account_count",
{ controller: "accounts", action: "index", format: "json", include: ["sub_account_count"] },
{})
expect(json.pluck("sub_account_count")).to match_array([0, 2])
end
context "when the includes query param includes 'global_id'" do
it "includes the account's global ID" do
json = api_call(