add group_category importer

fixes CORE-654

test plan
 - rake doc:api
 - sis docs should generate
 - group category importer should work

Change-Id: I015692ae5795f1dec5da291fe84330185bd7b3c7
Reviewed-on: https://gerrit.instructure.com/137878
Reviewed-by: Cody Cutrer <cody@instructure.com>
Tested-by: Jenkins
QA-Review: Tucker McKnight <tmcknight@instructure.com>
Product-Review: Rob Orton <rob@instructure.com>
This commit is contained in:
Rob Orton 2018-01-12 21:54:49 -07:00
parent 33c7ecc1ab
commit c0aa33d989
8 changed files with 305 additions and 4 deletions

View File

@ -28,6 +28,7 @@
<li><%= before_label(t(:enrollments_label, "Enrollments")) %> <%= counts[:enrollments] %></li>
<li><%= before_label(t(:crosslists_label, "Crosslists")) %> <%= counts[:xlists] %></li>
<li><%= before_label(t("Admins")) %> <%= counts[:admins] %></li>
<li><%= before_label(t("Group Categories")) %> <%= counts[:group_categories] %></li>
<li><%= before_label(t(:groups, "Groups")) %> <%= counts[:groups] %></li>
<li><%= before_label(t(:group_enrollments, "Group Enrollments")) %> <%= counts[:group_memberships] %></li>
<li><%= before_label(t("User Observers")) %> <%= counts[:user_observers] %></li>

View File

@ -734,6 +734,57 @@ E411208,13834,student,2A,active
E411208,13aa3,teacher,2A,active
</pre>
group_categories.csv
------------
<table class="sis_csv">
<tr>
<th>Field Name</th>
<th>Data Type</th>
<th>Required</th>
<th>Sticky</th>
<th>Description</th>
</tr>
<tr>
<td>group_category_id</td>
<td>text</td>
<td></td>
<td></td>
<td>A unique identifier used to reference a group category.
This identifier must not change for the group category, and must be globally unique.</td>
</tr>
<tr>
<td>account_id</td>
<td>text</td>
<td></td>
<td></td>
<td>The account identifier from accounts.csv, if none is specified the group
will be attached to the root account.</td>
</tr>
<tr>
<td>category_name</td>
<td>text</td>
<td></td>
<td></td>
<td>The name of the group category.</td>
</tr>
<tr>
<td>status</td>
<td>enum</td>
<td></td>
<td></td>
<td>active, deleted</td>
</tr>
</table>
Sample:
<pre>group_category_id,account_id,category_name,status
GC08,A001,First Group Category,active
GC07,,GC7,active
GC10,,GC10,deleted
</pre>
groups.csv
------------

View File

@ -0,0 +1,48 @@
#
# Copyright (C) 2018 - 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 SIS
module CSV
# note these are account-level group categories, not course groups
class GroupCategoryImporter < CSVBaseImporter
def self.group_category_csv?(row)
row.include?('group_category_id') && row.include?('category_name')
end
def self.identifying_fields
%w[group_id].freeze
end
# expected columns
# group_category_id, account_id, name, status
def process(csv)
@sis.counts[:group_categories] += SIS::GroupCategoryImporter.new(@root_account, importer_opts).process do |importer|
csv_rows(csv) do |row|
update_progress
begin
importer.add_group_category(row['group_category_id'], row['account_id'], row['category_name'], row['status'])
rescue ImportError => e
add_warning(csv, "#{e}")
end
end
end
end
end
end
end

View File

@ -35,7 +35,7 @@ module SIS
# * Course and Section must be imported before Xlist
# * Course, Section, and User must be imported before Enrollment
IMPORTERS = %i{change_sis_id account term abstract_course course section
xlist user enrollment admin group group_membership
xlist user enrollment admin group_category group group_membership
grade_publishing_results user_observer}.freeze
def initialize(root_account, opts = {})

View File

@ -0,0 +1,86 @@
#
# Copyright (C) 2018 - 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 SIS
class GroupCategoryImporter < BaseImporter
def process
start = Time.now
importer = Work.new(@batch, @root_account, @logger)
yield importer
@logger.debug("Group categories took #{Time.now - start} seconds")
return importer.success_count
end
private
class Work
attr_accessor :success_count
def initialize(batch, root_account, logger)
@batch = batch
@root_account = root_account
@logger = logger
@success_count = 0
@accounts_cache = {}
end
def add_group_category(sis_id, account_id, category_name, status)
raise ImportError, "No sis_id given for a group category" if sis_id.blank?
raise ImportError, "No name given for group category #{sis_id}" if category_name.blank?
raise ImportError, "No status given for group category #{sis_id}" if status.blank?
raise ImportError, "Improper status \"#{status}\" for group category #{sis_id}, skipping" unless status =~ /\A(active|deleted)/i
@logger.debug("Processing Group Category #{[sis_id, account_id, category_name, status].inspect}")
account = nil
if account_id.present?
account = @accounts_cache[account_id]
account ||= @root_account.all_accounts.where(sis_source_id: account_id).take
raise ImportError, "Account with id \"#{account_id}\" didn't exist for group category #{sis_id}" unless account
@accounts_cache[account.sis_source_id] = account
end
account ||= @root_account
gc = @root_account.all_group_categories.where(sis_source_id: sis_id).take
gc ||= account.group_categories.new
gc.name = category_name
gc.context = account
gc.root_account_id = @root_account.id
gc.sis_source_id = sis_id
gc.sis_batch_id = @batch.id if @batch
case status
when /active/i
gc.deleted_at = nil
when /deleted/i
gc.deleted_at = Time.zone.now
end
if gc.save
@success_count += 1
else
msg = "A group category did not pass validation (group category: #{sis_id}, error: "
msg += gc.errors.full_messages.join(",") + ")"
raise ImportError, msg
end
end
end
end
end

View File

@ -150,6 +150,7 @@ describe SisImportsApiController, type: :request do
"users" => 1,
"user_observers" => 0,
"xlists" => 0,
"group_categories" => 0,
"groups" => 0,
"group_memberships" => 0,
"terms" => 0, }},
@ -664,6 +665,7 @@ describe SisImportsApiController, type: :request do
"users" => 0,
"user_observers" => 0,
"xlists" => 0,
"group_categories" => 0,
"groups" => 0,
"group_memberships" => 0,
"terms" => 0, }},

View File

@ -0,0 +1,111 @@
#
# Copyright (C) 2018 - 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.rb')
describe SIS::CSV::GroupCategoryImporter do
before(:once) do
account_model
process_csv_data_cleanly(
"account_id,parent_account_id,name,status",
"A001,,TestAccount,active"
)
end
it "should skip bad content" do
before_count = GroupCategory.count
importer = process_csv_data(
"group_category_id,account_id,category_name,status",
"GC001,A001,Group Cat 1,active",
"Gc002,A001,Group Cat 2,blerged",
"Gc003,A001,,active",
"Gc004,invalid,Group Cat 4,active",
",A001,G1,active")
expect(importer.errors).to eq []
expect(importer.warnings.map(&:last)).to eq(
["Improper status \"blerged\" for group category Gc002, skipping",
"No name given for group category Gc003",
"Account with id \"invalid\" didn't exist for group category Gc004",
"No sis_id given for a group category"]
)
expect(GroupCategory.count).to eq before_count + 1
end
it "should create group categories" do
@sub = Account.where(sis_source_id: 'A001').take
process_csv_data_cleanly(
"group_category_id,account_id,category_name,status",
"Gc001,,Group Cat 1,active",
"Gc002,A001,Group Cat 2,active")
group_category = GroupCategory.where(sis_source_id: 'Gc001').take
expect(group_category.context_id).to eq @account.id
expect(group_category.sis_source_id).to eq 'Gc001'
expect(group_category.name).to eq "Group Cat 1"
expect(group_category.deleted_at).to be_nil
group_category2 = GroupCategory.where(sis_source_id: 'Gc002').take
expect(group_category2.context_id).to eq @sub.id
end
it "should allow moving group categories" do
@sub = Account.where(sis_source_id: 'A001').take
process_csv_data_cleanly(
"group_category_id,account_id,category_name,status",
"Gc001,,Group Cat 1,active",
"Gc002,A001,Group Cat 2,active")
group_category = GroupCategory.where(sis_source_id: 'Gc001').take
expect(group_category.context_id).to eq @account.id
group_category2 = GroupCategory.where(sis_source_id: 'Gc002').take
expect(group_category2.context_id).to eq @sub.id
process_csv_data_cleanly(
"group_category_id,account_id,category_name,status",
"Gc001,A001,Group Cat 1,active",
"Gc002,,Group Cat 2,active")
expect(group_category.reload.context_id).to eq @sub.id
expect(group_category2.reload.context_id).to eq @account.id
end
it "should fail model validations" do
@sub = Account.where(sis_source_id: 'A001').take
importer = process_csv_data(
"group_category_id,account_id,category_name,status",
"Gc001,,Group Cat 1,active",
"Gc002,,Group Cat 1,active")
expect(importer.errors).to eq []
expect(importer.warnings.map(&:last)).to eq(["A group category did not pass validation (group category: Gc002, error: Name Group Cat 1 is already in use.)"])
end
it "should delete and restore group categories" do
process_csv_data_cleanly(
"group_category_id,account_id,category_name,status",
"Gc001,,Group Cat 1,active",
"Gc002,A001,Group Cat 2,deleted")
group_category= GroupCategory.where(sis_source_id: 'Gc001').take
expect(group_category.deleted_at).to be_nil
group_category2= GroupCategory.where(sis_source_id: 'Gc002').take
expect(group_category2.deleted_at).to_not be_nil
process_csv_data_cleanly(
"group_category_id,account_id,category_name,status",
"Gc001,,Group Cat 1,deleted",
"Gc002,A001,Group Cat 2,active")
expect(group_category.reload.deleted_at).to_not be_nil
expect(group_category2.reload.deleted_at).to be_nil
end
end

View File

@ -23,14 +23,16 @@ describe "accounts/_sis_batch_counts.html.erb" do
it "should render sis count data" do
data = {counts: {xlists: 2, enrollments: 3, courses: 5, users: 6, terms: 6,
group_memberships: 7, groups: 8, sections: 9, accounts: 10,
admins: 1, user_observers: 3, change_sis_id: 0}}
group_memberships: 7, group_categories: 2, groups: 8,
sections: 9, accounts: 10, admins: 1, user_observers: 3,
change_sis_id: 0}}
report = double()
expect(report).to receive(:data).and_return(data)
render :partial => 'accounts/sis_batch_counts', :object => report
map = {xlists: "Crosslists", group_memberships: "Group Enrollments",
user_observers: "User Observers", change_sis_id: "Change SIS IDs"}
user_observers: "User Observers", change_sis_id: "Change SIS IDs",
group_categories: "Group Categories",}
data[:counts].each_pair do |type, count|
name = map[type] || type.to_s.capitalize