bearcat 1.0.0 → 1.5.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bearcat.gemspec +15 -5
- data/lib/badgrcat/api_array.rb +25 -0
- data/lib/badgrcat/client/methods.rb +54 -0
- data/lib/badgrcat/client.rb +53 -0
- data/lib/badgrcat/version.rb +3 -0
- data/lib/bearcat/api_array.rb +132 -65
- data/lib/bearcat/client/account_reports.rb +6 -14
- data/lib/bearcat/client/accounts.rb +18 -6
- data/lib/bearcat/client/analytics.rb +12 -0
- data/lib/bearcat/client/assignment_groups.rb +15 -0
- data/lib/bearcat/client/assignments.rb +17 -9
- data/lib/bearcat/client/blueprint_courses.rb +25 -0
- data/lib/bearcat/client/calendar_events.rb +9 -17
- data/lib/bearcat/client/canvas_files.rb +0 -2
- data/lib/bearcat/client/conferences.rb +3 -8
- data/lib/bearcat/client/content_exports.rb +39 -0
- data/lib/bearcat/client/content_migrations.rb +54 -0
- data/lib/bearcat/client/conversations.rb +3 -8
- data/lib/bearcat/client/courses.rb +25 -14
- data/lib/bearcat/client/custom_gradebook_columns.rb +21 -0
- data/lib/bearcat/client/discussions.rb +10 -4
- data/lib/bearcat/client/enrollments.rb +9 -25
- data/lib/bearcat/client/external_tools.rb +18 -0
- data/lib/bearcat/client/file_helper.rb +36 -30
- data/lib/bearcat/client/files.rb +9 -0
- data/lib/bearcat/client/folders.rb +24 -0
- data/lib/bearcat/client/graph_ql.rb +17 -0
- data/lib/bearcat/client/group_categories.rb +18 -0
- data/lib/bearcat/client/group_memberships.rb +14 -0
- data/lib/bearcat/client/groups.rb +10 -2
- data/lib/bearcat/client/learning_outcomes.rb +17 -0
- data/lib/bearcat/client/logins.rb +20 -0
- data/lib/bearcat/client/module_items.rb +18 -0
- data/lib/bearcat/client/modules.rb +12 -7
- data/lib/bearcat/client/o_auth2.rb +18 -9
- data/lib/bearcat/client/outcome_groups.rb +2 -4
- data/lib/bearcat/client/outcome_imports.rb +48 -0
- data/lib/bearcat/client/outcomes.rb +4 -7
- data/lib/bearcat/client/pages.rb +15 -0
- data/lib/bearcat/client/progresses.rb +9 -0
- data/lib/bearcat/client/quizzes.rb +13 -9
- data/lib/bearcat/client/reports.rb +37 -17
- data/lib/bearcat/client/roles.rb +15 -0
- data/lib/bearcat/client/rubric.rb +17 -0
- data/lib/bearcat/client/rubric_assessment.rb +13 -0
- data/lib/bearcat/client/rubric_association.rb +13 -0
- data/lib/bearcat/client/search.rb +9 -0
- data/lib/bearcat/client/sections.rb +10 -17
- data/lib/bearcat/client/sis_imports.rb +6 -12
- data/lib/bearcat/client/submissions.rb +53 -21
- data/lib/bearcat/client/tabs.rb +12 -0
- data/lib/bearcat/client/users.rb +32 -13
- data/lib/bearcat/client.rb +111 -45
- data/lib/bearcat/client_module.rb +103 -0
- data/lib/bearcat/rate_limiting/increment_bucket.lua +33 -0
- data/lib/bearcat/rate_limiting/redis_script.rb +164 -0
- data/lib/bearcat/rate_limiting.rb +69 -0
- data/lib/bearcat/redis_connection.rb +106 -0
- data/lib/bearcat/spec_helpers.rb +125 -0
- data/lib/bearcat/version.rb +1 -1
- data/lib/bearcat.rb +49 -0
- data/lib/catalogcat/api_array.rb +22 -0
- data/lib/catalogcat/client/catalogs.rb +21 -0
- data/lib/catalogcat/client/certificates.rb +17 -0
- data/lib/catalogcat/client/courses.rb +25 -0
- data/lib/catalogcat/client/email_domain_sets.rb +17 -0
- data/lib/catalogcat/client/enrollments.rb +25 -0
- data/lib/catalogcat/client/orders.rb +13 -0
- data/lib/catalogcat/client/user_registrations.rb +9 -0
- data/lib/catalogcat/client.rb +26 -0
- data/lib/catalogcat/version.rb +3 -0
- data/lib/catalogcat.rb +14 -0
- data/spec/bearcat/api_array_spec.rb +112 -0
- data/spec/bearcat/client/accounts_spec.rb +71 -1
- data/spec/bearcat/client/analytics_spec.rb +22 -0
- data/spec/bearcat/client/assignment_groups_spec.rb +47 -0
- data/spec/bearcat/client/assignments_spec.rb +43 -0
- data/spec/bearcat/client/blueprint_courses_spec.rb +43 -0
- data/spec/bearcat/client/canvas_files_spec.rb +1 -2
- data/spec/bearcat/client/content_exports_spec.rb +68 -0
- data/spec/bearcat/client/content_migrations_spec.rb +94 -0
- data/spec/bearcat/client/courses_spec.rb +81 -2
- data/spec/bearcat/client/custom_gradebook_columns_spec.rb +66 -0
- data/spec/bearcat/client/discussions_spec.rb +73 -0
- data/spec/bearcat/client/enrollments_spec.rb +10 -0
- data/spec/bearcat/client/external_tools_spec.rb +106 -0
- data/spec/bearcat/client/files_spec.rb +15 -0
- data/spec/bearcat/client/folders_spec.rb +18 -0
- data/spec/bearcat/client/graph_ql_spec.rb +35 -0
- data/spec/bearcat/client/group_categories_spec.rb +45 -0
- data/spec/bearcat/client/group_membership_spec.rb +14 -0
- data/spec/bearcat/client/group_memberships_spec.rb +36 -0
- data/spec/bearcat/client/groups_spec.rb +46 -0
- data/spec/bearcat/client/learning_outcomes_spec.rb +25 -0
- data/spec/bearcat/client/module_items_spec.rb +60 -0
- data/spec/bearcat/client/modules_spec.rb +38 -1
- data/spec/bearcat/client/o_auth2_spec.rb +3 -3
- data/spec/bearcat/client/pages_spec.rb +17 -0
- data/spec/bearcat/client/quizzes_spec.rb +41 -4
- data/spec/bearcat/client/reports_spec.rb +40 -1
- data/spec/bearcat/client/roles_spec.rb +24 -0
- data/spec/bearcat/client/rubric_assessment_spec.rb +47 -0
- data/spec/bearcat/client/rubric_association_spec.rb +39 -0
- data/spec/bearcat/client/rubric_spec.rb +45 -0
- data/spec/bearcat/client/search_spec.rb +16 -0
- data/spec/bearcat/client/sections_spec.rb +12 -0
- data/spec/bearcat/client/submissions_spec.rb +47 -2
- data/spec/bearcat/client/users_spec.rb +43 -0
- data/spec/bearcat/client_spec.rb +1 -4
- data/spec/bearcat/rate_limiting_spec.rb +62 -0
- data/spec/bearcat/stub_bearcat_spec.rb +15 -0
- data/spec/fixtures/access_token.json +3 -0
- data/spec/fixtures/account_admin_create.json +14 -0
- data/spec/fixtures/account_admin_delete.json +15 -0
- data/spec/fixtures/account_admins.json +54 -0
- data/spec/fixtures/account_courses.json +48 -0
- data/spec/fixtures/account_grading_standards.json +20 -0
- data/spec/fixtures/account_groups.json +42 -0
- data/spec/fixtures/account_role.json +34 -0
- data/spec/fixtures/account_roles.json +35 -0
- data/spec/fixtures/account_sis_imports.json +39 -0
- data/spec/fixtures/account_sub_accounts.json +17 -0
- data/spec/fixtures/accounts.json +13 -0
- data/spec/fixtures/assignment.json +32 -0
- data/spec/fixtures/assignment_group.json +7 -0
- data/spec/fixtures/assignment_groups.json +16 -0
- data/spec/fixtures/blueprint_migration.json +12 -0
- data/spec/fixtures/blueprint_subscriptions.json +5 -0
- data/spec/fixtures/blueprint_template.json +7 -0
- data/spec/fixtures/blueprint_update_assocations_success.json +3 -0
- data/spec/fixtures/communication_channels.json +10 -0
- data/spec/fixtures/content_export.json +9 -0
- data/spec/fixtures/content_migration_files/content_migration.json +13 -0
- data/spec/fixtures/course_copy.json +18 -0
- data/spec/fixtures/course_files.json +38 -0
- data/spec/fixtures/course_folder.json +21 -0
- data/spec/fixtures/course_folders.json +44 -0
- data/spec/fixtures/course_grading_standards.json +20 -0
- data/spec/fixtures/course_settings.json +33 -0
- data/spec/fixtures/create_course_discussion.json +44 -0
- data/spec/fixtures/created_group.json +37 -0
- data/spec/fixtures/created_group_category.json +15 -0
- data/spec/fixtures/created_group_membership.json +8 -0
- data/spec/fixtures/created_module.json +13 -0
- data/spec/fixtures/custom_gradebook_columns/column_data.json +4 -0
- data/spec/fixtures/custom_gradebook_columns/custom_gradebook_column.json +7 -0
- data/spec/fixtures/custom_gradebook_columns/custom_gradebook_columns.json +16 -0
- data/spec/fixtures/custom_gradebook_columns/gradebook_column_progress.json +14 -0
- data/spec/fixtures/dashboard.json +6 -0
- data/spec/fixtures/delete_course.json +3 -0
- data/spec/fixtures/delete_group_category.json +3 -0
- data/spec/fixtures/deleted_group.json +37 -0
- data/spec/fixtures/department_level_participation.json +73 -0
- data/spec/fixtures/department_level_statistics.json +10 -0
- data/spec/fixtures/discussion_entries.json +21 -0
- data/spec/fixtures/discussion_entry_replies.json +21 -0
- data/spec/fixtures/discussion_topic.json +49 -0
- data/spec/fixtures/discussion_topics.json +51 -0
- data/spec/fixtures/edited_group.json +129 -0
- data/spec/fixtures/edited_group_category.json +15 -0
- data/spec/fixtures/enrollment_terms.json +1 -1
- data/spec/fixtures/external_tool.json +55 -0
- data/spec/fixtures/external_tools.json +57 -0
- data/spec/fixtures/file.csv +5 -0
- data/spec/fixtures/gradebook_history.json +52 -0
- data/spec/fixtures/graph_ql_scores.json +33 -0
- data/spec/fixtures/group.json +15 -0
- data/spec/fixtures/group_categories.json +28 -0
- data/spec/fixtures/group_category.json +13 -0
- data/spec/fixtures/group_category_groups.json +20 -0
- data/spec/fixtures/group_membership.json +7 -0
- data/spec/fixtures/learning_outcome.json +32 -0
- data/spec/fixtures/merge_user.json +8 -0
- data/spec/fixtures/module.json +15 -0
- data/spec/fixtures/module_item.json +15 -0
- data/spec/fixtures/module_items.json +47 -0
- data/spec/fixtures/ok.json +3 -0
- data/spec/fixtures/outcome_result.json +13 -0
- data/spec/fixtures/pages.json +40 -0
- data/spec/fixtures/progress.json +13 -0
- data/spec/fixtures/quizzes/course_quiz_questions.json +59 -0
- data/spec/fixtures/quizzes/quiz_assignment_override.json +31 -0
- data/spec/fixtures/reactivate_enrollment.json +20 -0
- data/spec/fixtures/rubric.json +13 -0
- data/spec/fixtures/rubric_assessment.json +32 -0
- data/spec/fixtures/rubric_association.json +13 -0
- data/spec/fixtures/search_find_recipients.json +10 -0
- data/spec/fixtures/update_section.json +1 -1
- data/spec/fixtures/user_details.json +16 -0
- data/spec/fixtures/user_logins.json +9 -0
- data/spec/helper.rb +2 -0
- metadata +336 -43
@@ -0,0 +1,48 @@
|
|
1
|
+
module Bearcat
|
2
|
+
class Client < Footrest::Client
|
3
|
+
module OutcomeImports
|
4
|
+
|
5
|
+
def import_outcomes(file_path, params={})
|
6
|
+
params = params.with_indifferent_access
|
7
|
+
params['attachment'] = Faraday::UploadIO.new(file_path, 'text/csv')
|
8
|
+
url = "api/v1/#{outcome_import_context_slug(params)}"
|
9
|
+
url += "group/#{params[:group]}/" if params[:group].present?
|
10
|
+
params.delete(:group)
|
11
|
+
post(url, params)
|
12
|
+
end
|
13
|
+
|
14
|
+
def outcome_import_status(id, params={})
|
15
|
+
params = params.with_indifferent_access
|
16
|
+
get("api/v1/#{outcome_import_context_slug(params)}#{id}", params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def outcome_import_created_group_ids(id, params={})
|
20
|
+
params = params.with_indifferent_access
|
21
|
+
get("api/v1/#{outcome_import_context_slug(params)}#{id}/created_group_ids", params)
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def outcome_import_context_slug(params)
|
27
|
+
context_hash = params.select { |k, _| k == "account" || k == "course" }
|
28
|
+
|
29
|
+
if context_hash.keys.count > 1
|
30
|
+
raise ArgumentError, "cannot have account and course in params"
|
31
|
+
elsif context_hash.empty?
|
32
|
+
"accounts/self/outcome_imports/"
|
33
|
+
else
|
34
|
+
context_hash_key = context_hash.keys.first
|
35
|
+
case context_hash_key
|
36
|
+
when 'account'
|
37
|
+
params.delete(:account)
|
38
|
+
"accounts/#{context_hash[context_hash_key]}/outcome_imports/"
|
39
|
+
when 'course'
|
40
|
+
params.delete(:course)
|
41
|
+
"courses/#{context_hash[context_hash_key]}/outcome_imports/"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,15 +1,12 @@
|
|
1
1
|
module Bearcat
|
2
2
|
class Client < Footrest::Client
|
3
3
|
module Outcomes
|
4
|
+
extend ClientModule
|
4
5
|
|
5
|
-
|
6
|
-
get
|
6
|
+
prefix "/api/v1/outcomes/:outcome/" do
|
7
|
+
get :show_outcome
|
8
|
+
put :update_outcome
|
7
9
|
end
|
8
|
-
|
9
|
-
def update_outcome(id, params={})
|
10
|
-
put("/api/v1/outcomes/#{id}", params)
|
11
|
-
end
|
12
|
-
|
13
10
|
end
|
14
11
|
end
|
15
12
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Bearcat
|
2
|
+
class Client < Footrest::Client
|
3
|
+
module Pages
|
4
|
+
extend ClientModule
|
5
|
+
|
6
|
+
context_types %i[course group] do |ct|
|
7
|
+
prefix "/api/v1/#{ct}s/:#{ct}/pages/" do
|
8
|
+
get :"list_#{ct}_pages"
|
9
|
+
post :"add_#{ct}_page"
|
10
|
+
get :"show_#{ct}_page", ":url"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,17 +1,21 @@
|
|
1
1
|
module Bearcat
|
2
2
|
class Client < Footrest::Client
|
3
3
|
module Quizzes
|
4
|
+
extend ClientModule
|
4
5
|
|
5
|
-
|
6
|
-
get
|
7
|
-
|
8
|
-
|
9
|
-
def quiz(course, quiz, params={})
|
10
|
-
get("/api/v1/courses/#{course}/quizzes/#{quiz}", params)
|
11
|
-
end
|
6
|
+
prefix "/api/v1/courses/:course/" do
|
7
|
+
get :list_course_quizzes, "quizzes"
|
8
|
+
post :create_quiz, "quizzes"
|
9
|
+
get :quiz_assignment_overrides, "quizzes/assignment_overrides"
|
12
10
|
|
13
|
-
|
14
|
-
|
11
|
+
prefix "quizzes/:quiz/" do
|
12
|
+
get :quiz
|
13
|
+
put :edit_quiz
|
14
|
+
post :quiz_extensions, "extensions"
|
15
|
+
get :quiz_questions, "questions"
|
16
|
+
post :create_quiz_report, "reports"
|
17
|
+
get :get_quiz_report, "reports/:report"
|
18
|
+
end
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
@@ -1,27 +1,47 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
1
3
|
module Bearcat
|
2
4
|
class Client < Footrest::Client
|
3
5
|
module Reports
|
6
|
+
extend ClientModule
|
4
7
|
|
5
|
-
|
6
|
-
get
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
def report_history(account, report_name)
|
14
|
-
get("/api/v1/accounts/#{account}/reports/#{report_name}")
|
15
|
-
end
|
16
|
-
|
17
|
-
def report_status(account, report_name, report_id)
|
18
|
-
get("/api/v1/accounts/#{account}/reports/#{report_name}/#{report_id}")
|
8
|
+
prefix "/api/v1/accounts/:account/reports/" do
|
9
|
+
get :report_list
|
10
|
+
post :start_report, ":report_name"
|
11
|
+
get :report_history, ":report_name"
|
12
|
+
get :report_status, ":report_name/:report_id"
|
13
|
+
delete :delete_report, ":report_name/:report_id"
|
19
14
|
end
|
20
15
|
|
21
|
-
def
|
22
|
-
|
16
|
+
def download_report(url, save_location=nil)
|
17
|
+
#This method takes the verified URL returned in a Canvas report (attachment['url']), and if
|
18
|
+
#a save_location is included, it will download it in chunks to the disk to save memory. You
|
19
|
+
#can also download the report to memory if you do not include a save location.
|
20
|
+
attempts = 0
|
21
|
+
begin
|
22
|
+
uri = URI.parse(url)
|
23
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
24
|
+
http.use_ssl = true if url.start_with?('https')
|
25
|
+
response = http.head(uri.to_s)
|
26
|
+
url = response['Location']
|
27
|
+
attempts += 1
|
28
|
+
end while attempts <= 5 && (response.code == '301' || response.code == '302' || response.code == '307')
|
29
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
30
|
+
http.use_ssl = true if uri.to_s.start_with?('https')
|
31
|
+
if save_location
|
32
|
+
File.open(save_location, 'wb') do |file|
|
33
|
+
http.request_get(uri.to_s) do |resp|
|
34
|
+
resp.read_body do |segment|
|
35
|
+
file.write(segment)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
else
|
40
|
+
response = http.request_get(uri.to_s)
|
41
|
+
CSV.parse(response.read_body, headers: true)
|
42
|
+
end
|
23
43
|
end
|
24
44
|
|
25
45
|
end
|
26
46
|
end
|
27
|
-
end
|
47
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Bearcat
|
2
|
+
class Client < Footrest::Client
|
3
|
+
module Roles
|
4
|
+
extend ClientModule
|
5
|
+
|
6
|
+
def roles(account_id='self', params={})
|
7
|
+
get("/api/v1/accounts/#{account_id}/roles", params)
|
8
|
+
end
|
9
|
+
|
10
|
+
def role(role_id, account_id='self', params={})
|
11
|
+
get("/api/v1/accounts/#{account_id}/roles/#{role_id}", params)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Bearcat
|
2
|
+
class Client < Footrest::Client
|
3
|
+
module Rubric
|
4
|
+
extend ClientModule
|
5
|
+
|
6
|
+
prefix "/api/v1/courses/:course/rubrics/" do
|
7
|
+
get :course_rubric, ":rubric"
|
8
|
+
get :course_rubrics
|
9
|
+
post :create_course_rubric
|
10
|
+
put :update_course_rubric, ":rubric"
|
11
|
+
delete :delete_course_rubric, ":rubric"
|
12
|
+
end
|
13
|
+
|
14
|
+
get :account_rubrics, "api/v1/accounts/:account/rubrics"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Bearcat
|
2
|
+
class Client < Footrest::Client
|
3
|
+
module RubricAssessment
|
4
|
+
extend ClientModule
|
5
|
+
|
6
|
+
prefix "/api/v1/courses/:course/rubric_associations/:rubric_association/rubric_assessments/" do
|
7
|
+
post :create_course_rubric_assessment
|
8
|
+
put :update_course_rubric_assessment, ":rubric_assessment"
|
9
|
+
delete :delete_course_rubric_assessment, ":rubric_assessment"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Bearcat
|
2
|
+
class Client < Footrest::Client
|
3
|
+
module RubricAssociation
|
4
|
+
extend ClientModule
|
5
|
+
|
6
|
+
prefix "/api/v1/courses/:course/rubric_associations/" do
|
7
|
+
post :create_course_rubric_association
|
8
|
+
put :update_course_rubric_association, ":rubric_association"
|
9
|
+
delete :delete_course_rubric_association, ":rubric_association"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,27 +1,20 @@
|
|
1
1
|
module Bearcat
|
2
2
|
class Client < Footrest::Client
|
3
3
|
module Sections
|
4
|
+
extend ClientModule
|
4
5
|
|
5
|
-
|
6
|
-
get
|
6
|
+
prefix "/api/v1/sections/:section/" do
|
7
|
+
get :section
|
8
|
+
put :update_section
|
9
|
+
delete :delete_section
|
10
|
+
post :crosslist_section, "crosslist/:new_course_id"
|
11
|
+
delete :decrosslist_section, "crosslist"
|
7
12
|
end
|
8
13
|
|
9
|
-
|
10
|
-
get
|
14
|
+
prefix "/api/v1/courses/:course/sections/" do
|
15
|
+
get :course_sections
|
16
|
+
post :create_section
|
11
17
|
end
|
12
|
-
|
13
|
-
def create_section(course, params)
|
14
|
-
post("/api/v1/courses/#{course.to_s}/sections", params)
|
15
|
-
end
|
16
|
-
|
17
|
-
def update_section(section, params)
|
18
|
-
put("/api/v1/sections/#{section.to_s}", params)
|
19
|
-
end
|
20
|
-
|
21
|
-
def delete_section(section)
|
22
|
-
delete("/api/v1/sections/#{section.to_s}")
|
23
|
-
end
|
24
|
-
|
25
18
|
end
|
26
19
|
end
|
27
20
|
end
|
@@ -1,20 +1,14 @@
|
|
1
1
|
module Bearcat
|
2
2
|
class Client < Footrest::Client
|
3
3
|
module SisImports
|
4
|
-
|
5
|
-
|
6
|
-
post("/api/v1/accounts/#{account}/sis_imports",
|
7
|
-
end
|
8
|
-
|
9
|
-
def get_sis_imports(account)
|
10
|
-
binding.pry
|
11
|
-
get("/api/v1/accounts/#{account}/sis_imports")
|
4
|
+
def import_sis_data(account, file, options = {}, content_type: nil)
|
5
|
+
options['attachment'] = Faraday::UploadIO.new(file, content_type || "application/zip")
|
6
|
+
post("/api/v1/accounts/#{account}/sis_imports", options)
|
12
7
|
end
|
13
8
|
|
14
|
-
def
|
15
|
-
get("/api/v1/accounts/#{account}/sis_imports/#{
|
9
|
+
def sis_import_status(account, sis_id, options = {})
|
10
|
+
get("/api/v1/accounts/#{account}/sis_imports/#{sis_id}", options)
|
16
11
|
end
|
17
|
-
|
18
12
|
end
|
19
13
|
end
|
20
|
-
end
|
14
|
+
end
|
@@ -1,37 +1,69 @@
|
|
1
1
|
module Bearcat
|
2
2
|
class Client < Footrest::Client
|
3
3
|
module Submissions
|
4
|
+
extend ClientModule
|
4
5
|
|
5
|
-
|
6
|
-
get
|
6
|
+
prefix "/api/v1/courses/:course/assignments/:assignment/submissions/" do
|
7
|
+
get :get_course_submissions
|
8
|
+
get :user_course_assignment_submission, ":user"
|
7
9
|
end
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
context_types %i[course section] do |ct|
|
12
|
+
prefix "/api/v1/#{ct}s/:#{ct}/" do
|
13
|
+
get :"#{ct}_submissions", "students/submissions"
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
+
prefix "assignments/:assignment/submissions/" do
|
16
|
+
post :"#{ct}_submission"
|
17
|
+
put :"grade_#{ct}_submission", ":user"
|
18
|
+
post :"#{ct}_update_grades", "update_grades"
|
19
|
+
end
|
20
|
+
end
|
15
21
|
end
|
16
22
|
|
17
|
-
def course_file_upload_submission(course, assignment, user,
|
18
|
-
|
19
|
-
params['submission'] = {
|
20
|
-
'submission_type' => 'online_upload',
|
21
|
-
'file_ids'=> [response['id']]
|
22
|
-
}
|
23
|
-
course_submission(course, assignment, params)
|
23
|
+
def course_file_upload_submission(course, assignment, user, file_data, params={})
|
24
|
+
file_upload_submission(course, assignment, user, file_data, params, type: :course)
|
24
25
|
end
|
25
26
|
|
26
|
-
def section_file_upload_submission(section, assignment, user,
|
27
|
-
|
28
|
-
params['submission'] = {
|
29
|
-
'submission_type' => 'online_upload',
|
30
|
-
'file_ids'=> [response['id']]
|
31
|
-
}
|
32
|
-
section_submission(section, assignment, params)
|
27
|
+
def section_file_upload_submission(section, assignment, user, file_data, params={})
|
28
|
+
file_upload_submission(section, assignment, user, file_data, params, type: :section)
|
33
29
|
end
|
34
30
|
|
31
|
+
protected
|
32
|
+
|
33
|
+
# @param file_data One of an array of file_path strings or an array of Hashes, each being upload file params plus the file's path
|
34
|
+
def file_upload_submission(type_id, assignment, user, file_data=nil, params={}, type:)
|
35
|
+
raise ArgumentError, 'Invalid type' unless [:course, :section].include?(type)
|
36
|
+
raise ArgumentError, 'Must provide either file paths or fully formed submission params' if params.dig(:submission, :file_ids).nil? && file_data.nil?
|
37
|
+
|
38
|
+
params = params.with_indifferent_access
|
39
|
+
sub_params = if params.dig(:submission, :file_ids).present?
|
40
|
+
{}
|
41
|
+
else
|
42
|
+
file_data = Array.wrap(file_data)
|
43
|
+
file_ids = file_data.map do |datum|
|
44
|
+
if datum.is_a?(Hash) && datum[:file_path].present?
|
45
|
+
# datum is a param hash for upload_file API plus the file_path
|
46
|
+
path = datum[:file_path]
|
47
|
+
upload_params = datum.except(:file_path)
|
48
|
+
else
|
49
|
+
# treat datum as a file_path string
|
50
|
+
path = datum
|
51
|
+
upload_params = params
|
52
|
+
end
|
53
|
+
|
54
|
+
response = upload_file("/api/v1/#{type}s/#{type_id}/assignments/#{assignment}/submissions/#{user}/files", path, upload_params)
|
55
|
+
response['id']
|
56
|
+
end
|
57
|
+
|
58
|
+
{
|
59
|
+
submission_type: 'online_upload',
|
60
|
+
file_ids: file_ids,
|
61
|
+
}
|
62
|
+
end
|
63
|
+
sub_params.merge!(params[:submission]) if params[:submission].present?
|
64
|
+
params[:submission] = sub_params
|
65
|
+
send("#{type}_submission".to_sym, type_id, assignment, params)
|
66
|
+
end
|
35
67
|
end
|
36
68
|
end
|
37
69
|
end
|
data/lib/bearcat/client/users.rb
CHANGED
@@ -1,21 +1,34 @@
|
|
1
1
|
module Bearcat
|
2
2
|
class Client < Footrest::Client
|
3
3
|
module Users
|
4
|
+
extend ClientModule
|
4
5
|
|
5
|
-
|
6
|
-
get
|
6
|
+
prefix "/api/v1/accounts/:account/users/" do
|
7
|
+
get :list_users
|
8
|
+
post :add_user
|
7
9
|
end
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
prefix "/api/v1/users/" do
|
12
|
+
prefix ":user/" do
|
13
|
+
get :user_detail
|
14
|
+
get :user_avatars, "avatars"
|
15
|
+
get :user_profile, "profile"
|
16
|
+
get :user_logins, "logins"
|
17
|
+
get :communication_channels, "communication_channels"
|
18
|
+
get :page_views, "page_views"
|
19
|
+
put :user_merge, "merge_into/:merge_into_user"
|
20
|
+
get :user_assignments, "courses/:course/assignments"
|
21
|
+
get :dashboard_positions, "dashboard_positions"
|
22
|
+
put :update_dashboard_positions, "dashboard_positions"
|
23
|
+
end
|
16
24
|
|
17
|
-
|
18
|
-
|
25
|
+
prefix "self/" do
|
26
|
+
get :course_nicknames, "course_nicknames"
|
27
|
+
get :course_nickname, "course_nicknames/:course"
|
28
|
+
put :set_course_nickname, "course_nicknames/:course"
|
29
|
+
delete :delete_course_nickname, "course_nicknames/:course"
|
30
|
+
delete :clear_course_nickname, "course_nicknames"
|
31
|
+
end
|
19
32
|
end
|
20
33
|
|
21
34
|
# scope: food
|
@@ -40,8 +53,14 @@ module Bearcat
|
|
40
53
|
delete("/api/v1/users/#{user}/custom_data/#{scope}", params)
|
41
54
|
end
|
42
55
|
|
43
|
-
def
|
44
|
-
|
56
|
+
def favorite_courses(user, params = {})
|
57
|
+
params.merge!({as_user_id: user})
|
58
|
+
get("/api/v1/users/self/favorites/courses", params)
|
59
|
+
end
|
60
|
+
|
61
|
+
def unfavorite_course(user, course, params = {})
|
62
|
+
params.merge!({as_user_id: user})
|
63
|
+
delete("/api/v1/users/self/favorites/courses/#{course}", params)
|
45
64
|
end
|
46
65
|
end
|
47
66
|
end
|
data/lib/bearcat/client.rb
CHANGED
@@ -1,54 +1,120 @@
|
|
1
|
+
require 'active_support/core_ext/hash'
|
1
2
|
require 'footrest/client'
|
3
|
+
require_relative 'rate_limiting'
|
4
|
+
require_relative 'client_module'
|
5
|
+
require_relative 'redis_connection'
|
6
|
+
|
2
7
|
module Bearcat
|
3
8
|
class Client < Footrest::Client
|
4
9
|
require 'bearcat/api_array'
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
require 'bearcat/client/quizzes'
|
25
|
-
|
26
|
-
include Assignments
|
27
|
-
include Accounts
|
28
|
-
include Courses
|
29
|
-
include Enrollments
|
30
|
-
include OutcomeGroups
|
31
|
-
include Outcomes
|
32
|
-
include Sections
|
33
|
-
include OAuth2
|
34
|
-
include Groups
|
35
|
-
include Conferences
|
36
|
-
include Users
|
37
|
-
include Reports
|
38
|
-
include Submissions
|
39
|
-
include Conversations
|
40
|
-
include Modules
|
41
|
-
include CanvasFiles
|
42
|
-
include CalendarEvents
|
43
|
-
include Discussions
|
44
|
-
include FileHelper
|
45
|
-
include Quizzes
|
46
|
-
|
47
|
-
|
48
|
-
# Override Footrest request for ApiArray support
|
10
|
+
|
11
|
+
@added_modules = []
|
12
|
+
|
13
|
+
Dir[File.join(__dir__, 'client', '*.rb')].each do |mod|
|
14
|
+
mname = File.basename(mod, '.*').camelize
|
15
|
+
mname = 'GraphQL' if mname == 'GraphQl'
|
16
|
+
require mod
|
17
|
+
lmod = "Bearcat::Client::#{mname}".constantize
|
18
|
+
include lmod
|
19
|
+
@added_modules << lmod
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.registered_endpoints
|
23
|
+
@registered_endpoints ||= @added_modules.reduce({}) do |h, m|
|
24
|
+
h.merge!(m._registered_endpoints) rescue h
|
25
|
+
end
|
26
|
+
@registered_endpoints
|
27
|
+
end
|
28
|
+
|
49
29
|
def request(method, &block)
|
50
|
-
|
30
|
+
response = rate_limited_request do
|
31
|
+
connection.send(method, &block)
|
32
|
+
end
|
33
|
+
ApiArray.process_response(response, self)
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_connection(config)
|
37
|
+
super
|
38
|
+
connection.builder.insert(Footrest::RaiseFootrestErrors, ExtendedRaiseFootrestErrors)
|
39
|
+
connection.builder.delete(Footrest::RaiseFootrestErrors)
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def rate_limited_request
|
45
|
+
return yield unless rate_limiter
|
46
|
+
|
47
|
+
canvas_rate_limits= 0
|
48
|
+
response = nil
|
49
|
+
|
50
|
+
begin
|
51
|
+
rate_limiter.apply(
|
52
|
+
rate_limit_key,
|
53
|
+
max_sleep: Bearcat.max_sleep_seconds,
|
54
|
+
on_sleep: ->(tts) {
|
55
|
+
message = "Canvas API rate limit reached; sleeping for #{tts.to_i} second(s) to catch up."
|
56
|
+
Bearcat.logger.debug(message)
|
57
|
+
},
|
58
|
+
) do
|
59
|
+
response = yield
|
60
|
+
0
|
61
|
+
end
|
62
|
+
rescue Footrest::HttpError::Forbidden => err
|
63
|
+
# Somehow our rate-limiting didn't limit enough and Canvas stopped us.
|
64
|
+
response = err.response
|
65
|
+
if canvas_rate_limits < 2 && err.message.include?("(Rate Limit Exceeded)")
|
66
|
+
canvas_rate_limits += 1
|
67
|
+
rate_limiter.checkin_known(rate_limit_key, 0)
|
68
|
+
|
69
|
+
message = "Canvas API applied rate limit; upticking Bearcat rate-limit avoidance and retrying (Retry #{canvas_rate_limits})."
|
70
|
+
Bearcat.logger.debug(message)
|
71
|
+
|
72
|
+
retry
|
73
|
+
end
|
74
|
+
raise
|
75
|
+
ensure
|
76
|
+
headers = response.try(:response_headers) || response.try(:headers) || {}
|
77
|
+
# -50 to provide a little extra leeway and hopefully be more proactive, making sure we don't even get close to Canvas throwing a 403, even if an out-of-band process is involved
|
78
|
+
rate_limiter.checkin_known(rate_limit_key, headers['x-rate-limit-remaining'].to_f - 100) if response
|
79
|
+
end
|
80
|
+
|
81
|
+
response
|
82
|
+
end
|
83
|
+
|
84
|
+
def rate_limiter
|
85
|
+
@rate_limiter ||= begin
|
86
|
+
rl = config[:rate_limiter] || Bearcat.rate_limiter
|
87
|
+
master_rate_limit = config[:master_rate_limit].present? ? config[:master_rate_limit] : Bearcat.master_rate_limit
|
88
|
+
|
89
|
+
if rl.nil? && master_rate_limit.nil? && defined?(Rails) && Rails.env.production? && defined?(::Redis) && Bearcat::RedisConnection.configured?("BEARCAT", explicit: false)
|
90
|
+
master_rate_limit = true
|
91
|
+
end
|
92
|
+
|
93
|
+
if rl.nil? && master_rate_limit
|
94
|
+
rl = RateLimiting::RedisLimiter
|
95
|
+
end
|
96
|
+
|
97
|
+
if rl.is_a?(Class)
|
98
|
+
rl.new()
|
99
|
+
elsif rl.present?
|
100
|
+
rl
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def rate_limit_key
|
108
|
+
Digest::SHA1.hexdigest(config[:token])
|
51
109
|
end
|
52
110
|
end
|
53
111
|
|
112
|
+
# Overridden response error middleware that, if an error code doesn't map to an exception, raises a more generic exception
|
113
|
+
class ExtendedRaiseFootrestErrors < Footrest::RaiseFootrestErrors
|
114
|
+
def on_complete(response)
|
115
|
+
super
|
116
|
+
key = response[:status].to_i
|
117
|
+
raise ERROR_MAP[key.floor(-2)], response if key >= 400
|
118
|
+
end
|
119
|
+
end
|
54
120
|
end
|