canvas_shim 0.1.0
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/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +21 -0
- data/app/assets/config/canvas_shim_manifest.js +2 -0
- data/app/assets/javascripts/canvas_shim/application.js +13 -0
- data/app/assets/stylesheets/canvas_shim/application.css +15 -0
- data/app/controllers/canvas_shim/application_controller.rb +12 -0
- data/app/controllers/canvas_shim/settings_api/swagger_controller.rb +11 -0
- data/app/controllers/canvas_shim/settings_api/v1/users_controller.rb +27 -0
- data/app/deploy/canvas_shim_asset_uploader.rb +19 -0
- data/app/helpers/canvas_shim/application_helper.rb +4 -0
- data/app/jobs/canvas_shim/application_job.rb +4 -0
- data/app/mailers/canvas_shim/application_mailer.rb +6 -0
- data/app/models/canvas_shim/application_record.rb +5 -0
- data/app/services/courses_service.rb +5 -0
- data/app/services/courses_service/commands/distribute_due_dates.rb +50 -0
- data/app/services/courses_service/commands/distribute_due_dates/scheduler.rb +70 -0
- data/app/services/grades_service.rb +33 -0
- data/app/services/grades_service/account.rb +13 -0
- data/app/services/grades_service/commands/zero_out_assignment_grades.rb +73 -0
- data/app/services/grades_service/commands/zero_out_assignment_grades_rollback.rb +38 -0
- data/app/services/pipeline_service.rb +27 -0
- data/app/services/pipeline_service/account.rb +9 -0
- data/app/services/pipeline_service/api/publish.rb +53 -0
- data/app/services/pipeline_service/commands/publish.rb +44 -0
- data/app/services/pipeline_service/commands/publish_events.rb +17 -0
- data/app/services/pipeline_service/endpoints/pipeline.rb +62 -0
- data/app/services/pipeline_service/endpoints/pipeline/message_builder.rb +75 -0
- data/app/services/pipeline_service/events/emitter.rb +56 -0
- data/app/services/pipeline_service/events/graded_out_event.rb +31 -0
- data/app/services/pipeline_service/events/http_client.rb +14 -0
- data/app/services/pipeline_service/events/responders/sis.rb +66 -0
- data/app/services/pipeline_service/events/subscription.rb +11 -0
- data/app/services/pipeline_service/http_client.rb +21 -0
- data/app/services/pipeline_service/logger.rb +39 -0
- data/app/services/pipeline_service/pipeline_client.rb +41 -0
- data/app/services/pipeline_service/serializers/assignment.rb +55 -0
- data/app/services/pipeline_service/serializers/base_methods.rb +33 -0
- data/app/services/pipeline_service/serializers/canvas_api_enrollment.rb +55 -0
- data/app/services/pipeline_service/serializers/enrollment.rb +31 -0
- data/app/services/pipeline_service/serializers/fetcher.rb +17 -0
- data/app/services/pipeline_service/serializers/submission.rb +30 -0
- data/app/services/pipeline_service/serializers/user.rb +31 -0
- data/app/services/settings_service.rb +51 -0
- data/app/services/settings_service/api_docs.yml +68 -0
- data/app/services/settings_service/assignment.rb +24 -0
- data/app/services/settings_service/assignment_repository.rb +89 -0
- data/app/services/settings_service/auth_middleware.rb +19 -0
- data/app/services/settings_service/auth_token.rb +21 -0
- data/app/services/settings_service/authenticator_stub.rb +9 -0
- data/app/services/settings_service/commands/get_enrollment_settings.rb +17 -0
- data/app/services/settings_service/commands/get_settings.rb +34 -0
- data/app/services/settings_service/commands/get_user_settings.rb +18 -0
- data/app/services/settings_service/commands/update_enrollment_setting.rb +22 -0
- data/app/services/settings_service/commands/update_settings.rb +38 -0
- data/app/services/settings_service/commands/update_user_setting.rb +21 -0
- data/app/services/settings_service/enrollment.rb +28 -0
- data/app/services/settings_service/queries/zero_grader_audit.rb +6 -0
- data/app/services/settings_service/repository.rb +93 -0
- data/app/services/settings_service/school.rb +26 -0
- data/app/services/settings_service/student_assignment.rb +24 -0
- data/app/services/settings_service/student_assignment_repository.rb +116 -0
- data/app/services/settings_service/submission.rb +24 -0
- data/app/services/settings_service/user.rb +28 -0
- data/app/views/layouts/canvas_shim/application.html.erb +14 -0
- data/config/initializers/hash.rb +17 -0
- data/config/initializers/string.rb +9 -0
- data/config/routes.rb +9 -0
- data/lib/canvas_shim.rb +5 -0
- data/lib/canvas_shim/engine.rb +12 -0
- data/lib/canvas_shim/version.rb +3 -0
- data/lib/tasks/canvas_shim_tasks.rake +7 -0
- metadata +214 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
module GradesService
|
2
|
+
module Commands
|
3
|
+
class ZeroOutAssignmentGradesRollback
|
4
|
+
def call!(options={})
|
5
|
+
load_audit(options)
|
6
|
+
|
7
|
+
CSV.foreach('/tmp/zero_grader_rollback.csv') do |row|
|
8
|
+
submission_id = row[0]
|
9
|
+
orig_score = row[1]
|
10
|
+
submission = Submission.find(submission_id)
|
11
|
+
user = submission.user
|
12
|
+
grader = GradesService::Account.account_admin
|
13
|
+
submission = Submission.find(submission_id)
|
14
|
+
assignment = submission.assignment
|
15
|
+
|
16
|
+
if orig_score.present? && submission.score == 0 || submission.score.nil?
|
17
|
+
puts "Setting submission #{submission.id} from #{submission.score} to #{orig_score or 'nil'}"
|
18
|
+
begin
|
19
|
+
puts "assignment.grade_student(#{user.id}, score: #{orig_score or 'nil'}, grader: 1)"
|
20
|
+
assignment.grade_student(user, score: orig_score, grader: grader)
|
21
|
+
rescue => e
|
22
|
+
puts "Failed Setting submission #{submission.id} from #{submission.score} to #{orig_score or 'nil'}: #{e}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def load_audit(options)
|
31
|
+
s3 = Aws::S3::Client.new(access_key_id: ENV['S3_ACCESS_KEY_ID'], secret_access_key: ENV['S3_ACCESS_KEY'])
|
32
|
+
File.open('/tmp/zero_grader_rollback.csv', 'w') do |file|
|
33
|
+
s3.get_object({ bucket: ENV['S3_BUCKET_NAME'], key: 'zero_grader/' + options[:log_file] }, target: file)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# The public api for the PipelineService
|
2
|
+
# By default the command will be enqueued.
|
3
|
+
#
|
4
|
+
# Example: PipelineService.publish(User.first)
|
5
|
+
module PipelineService
|
6
|
+
cattr_reader :queue_mode
|
7
|
+
def self.publish(object, api: API::Publish, noun: nil)
|
8
|
+
return if SettingsService.get_settings(object: :school, id: 1)['disable_pipeline']
|
9
|
+
api.new(object, noun: noun).call
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.perform_synchronously?
|
13
|
+
queue_mode == 'synchronous'
|
14
|
+
end
|
15
|
+
|
16
|
+
@@queue_mode = ENV['SYNCHRONOUS_PIPELINE_JOBS'] == 'true' ? 'synchronous' : 'asynchronous'
|
17
|
+
def self.queue_mode=(mode)
|
18
|
+
case mode
|
19
|
+
when 'synchronous'
|
20
|
+
@@queue_mode = 'synchronous'
|
21
|
+
when 'asynchronous'
|
22
|
+
@@queue_mode = 'asynchronous'
|
23
|
+
else
|
24
|
+
raise 'unknown queue mode: ' + mode
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# The API calls Commands
|
2
|
+
#
|
3
|
+
# Class methods map to Commands
|
4
|
+
# ie: PipelineService::API::Publish calls PipelineService::Commands::Publish
|
5
|
+
#
|
6
|
+
module PipelineService
|
7
|
+
module API
|
8
|
+
class Publish
|
9
|
+
def initialize(object, args={})
|
10
|
+
@object = object
|
11
|
+
@changes = object.try(:changes)
|
12
|
+
@noun = args[:noun]
|
13
|
+
@args = args
|
14
|
+
configure_dependencies
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
return if SettingsService.get_settings(object: :school, id: 1)['disable_pipeline']
|
19
|
+
queue.enqueue(self, priority: 1000000)
|
20
|
+
end
|
21
|
+
|
22
|
+
def perform
|
23
|
+
command.call
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :object, :jobs, :command_class, :queue, :noun, :changes
|
29
|
+
|
30
|
+
def configure_dependencies
|
31
|
+
@command_class = @args[:command_class] || Commands::Publish
|
32
|
+
@queue = @args[:queue] || Delayed::Job
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def subscriptions
|
37
|
+
Events::Subscription.new(
|
38
|
+
event: 'graded_out',
|
39
|
+
responder: Events::Responders::SIS
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def command
|
44
|
+
command_class.new(
|
45
|
+
object: object,
|
46
|
+
event_subscriptions: subscriptions,
|
47
|
+
noun: noun,
|
48
|
+
changes: changes
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module PipelineService
|
2
|
+
module Commands
|
3
|
+
# Serialize a canvas object into their API format and
|
4
|
+
# send to the pipeline
|
5
|
+
#
|
6
|
+
# Usage:
|
7
|
+
# Send.new(message: User.last).call
|
8
|
+
class Publish
|
9
|
+
attr_reader :message, :serializer
|
10
|
+
|
11
|
+
def initialize(args)
|
12
|
+
@args = args
|
13
|
+
@object = args[:object]
|
14
|
+
@changes = args[:changes]
|
15
|
+
configure_dependencies
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
return if SettingsService.get_settings(object: :school, id: 1)['disable_pipeline']
|
20
|
+
post_to_pipeline
|
21
|
+
publish_events unless changes.nil?
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :object, :client, :responder, :changes
|
28
|
+
|
29
|
+
def configure_dependencies
|
30
|
+
@client = @args[:client] || PipelineClient
|
31
|
+
end
|
32
|
+
|
33
|
+
def publish_events
|
34
|
+
Commands::PublishEvents.new(@args).call
|
35
|
+
end
|
36
|
+
|
37
|
+
def post_to_pipeline
|
38
|
+
client.new(
|
39
|
+
@args.merge(object: object)
|
40
|
+
).call
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module PipelineService
|
2
|
+
module Endpoints
|
3
|
+
class Pipeline
|
4
|
+
def initialize(args={})
|
5
|
+
@args = args
|
6
|
+
configure_dependencies
|
7
|
+
raise 'Missing config' if missing_config?
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
return if SettingsService.get_settings(object: :school, id: 1)['disable_pipeline']
|
12
|
+
if PipelineService.perform_synchronously?
|
13
|
+
perform
|
14
|
+
else
|
15
|
+
Delayed::Job.enqueue(self, priority: 1000000)
|
16
|
+
end
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def perform
|
21
|
+
configure_publisher
|
22
|
+
build_payload
|
23
|
+
post
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :endpoint, :username, :password, :publisher, :message_builder, :payload
|
29
|
+
|
30
|
+
def build_payload
|
31
|
+
@payload = message_builder.new(@args).call
|
32
|
+
end
|
33
|
+
|
34
|
+
def configure_dependencies
|
35
|
+
@message_builder = @args[:message_builder] || MessageBuilder
|
36
|
+
@publisher = @args[:publisher] || PipelinePublisher
|
37
|
+
@endpoint = ENV['PIPELINE_ENDPOINT']
|
38
|
+
@username = ENV['PIPELINE_USER_NAME']
|
39
|
+
@password = ENV['PIPELINE_PASSWORD']
|
40
|
+
end
|
41
|
+
|
42
|
+
def missing_config?
|
43
|
+
[endpoint, username, password].any?(&:nil?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def configure_publisher
|
47
|
+
publisher.configure do |config|
|
48
|
+
config.host = endpoint
|
49
|
+
config.username = username
|
50
|
+
config.password = password
|
51
|
+
if endpoint.slice(0,7) == 'http://'
|
52
|
+
config.scheme = 'http'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def post
|
58
|
+
PipelineService::HTTPClient.post(payload)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module PipelineService
|
2
|
+
module Endpoints
|
3
|
+
class Pipeline
|
4
|
+
class MessageBuilder
|
5
|
+
SOURCE = 'canvas'
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
@object = args[:object]
|
9
|
+
@serializer = args[:serializer]
|
10
|
+
@args = args
|
11
|
+
configure_dependencies
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
fetch_serializer
|
16
|
+
serialize
|
17
|
+
result = build
|
18
|
+
log
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :message_class, :object, :fetcher, :serializer, :canvas_domain, :logger, :serialized_object
|
25
|
+
|
26
|
+
def serialize
|
27
|
+
@serialized_object = serializer.new(object: object).call
|
28
|
+
end
|
29
|
+
|
30
|
+
def log
|
31
|
+
Logger.new(source: 'pipeline', message: payload).call
|
32
|
+
end
|
33
|
+
|
34
|
+
def payload
|
35
|
+
{
|
36
|
+
noun: noun,
|
37
|
+
meta: {
|
38
|
+
source: SOURCE,
|
39
|
+
domain_name: canvas_domain,
|
40
|
+
api_version: 1,
|
41
|
+
status: object.try(:state)
|
42
|
+
},
|
43
|
+
identifiers: { id: id },
|
44
|
+
data: serialized_object
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def configure_dependencies
|
49
|
+
@message_class = @args[:message_class] || PipelinePublisher::Message
|
50
|
+
@fetcher = @args[:fetcher] || Serializers::Fetcher
|
51
|
+
@logger = @args[:logger] || PipelineService::Logger
|
52
|
+
@canvas_domain = ENV['CANVAS_DOMAIN']
|
53
|
+
end
|
54
|
+
|
55
|
+
def fetch_serializer
|
56
|
+
return if @serializer
|
57
|
+
@serializer = fetcher.fetch(object: object)
|
58
|
+
end
|
59
|
+
|
60
|
+
def build
|
61
|
+
message_class.new(payload.to_hash)
|
62
|
+
end
|
63
|
+
|
64
|
+
def noun
|
65
|
+
object.class.to_s.underscore
|
66
|
+
end
|
67
|
+
|
68
|
+
def id
|
69
|
+
return object.id unless object.is_a?(Hash)
|
70
|
+
object[:id]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module PipelineService
|
2
|
+
module Events
|
3
|
+
class Emitter
|
4
|
+
def initialize(args={})
|
5
|
+
@object = args[:object]
|
6
|
+
@args = args
|
7
|
+
@responder = @args[:responder] || Events::Responders::SIS
|
8
|
+
@event = Events::GradedOutEvent
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
return unless object.is_a?(Enrollment)
|
13
|
+
fetch_serializer
|
14
|
+
build_message
|
15
|
+
build_responder
|
16
|
+
build_subscriptions
|
17
|
+
emit
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :subscriptions, :object, :responder, :message, :serializer, :event
|
23
|
+
|
24
|
+
def build_subscriptions
|
25
|
+
@subscriptions = [
|
26
|
+
Events::Subscription.new(
|
27
|
+
event: :graded_out,
|
28
|
+
responder: responder
|
29
|
+
)
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_responder
|
34
|
+
@responder = responder.new(
|
35
|
+
object: object,
|
36
|
+
message: message
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch_serializer
|
41
|
+
@serializer = Serializers::CanvasAPIEnrollment
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_message
|
45
|
+
@message = serializer.new(object: object).call
|
46
|
+
end
|
47
|
+
|
48
|
+
def emit
|
49
|
+
subscriptions.each do |subscription|
|
50
|
+
next if subscription.event != :graded_out
|
51
|
+
event.new(@args.merge(subscription: subscription)).emit
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module PipelineService
|
2
|
+
module Events
|
3
|
+
class GradedOutEvent
|
4
|
+
def initialize(args)
|
5
|
+
@args = args
|
6
|
+
@subscription = args[:subscription]
|
7
|
+
@object = args[:object]
|
8
|
+
@changes = args[:changes]
|
9
|
+
end
|
10
|
+
|
11
|
+
def emit
|
12
|
+
return unless should_trigger?
|
13
|
+
subscription.responder.call
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_accessor :subscription, :object, :changes
|
19
|
+
|
20
|
+
def should_trigger?
|
21
|
+
return unless object.is_a?(::Enrollment)
|
22
|
+
recently_completed?
|
23
|
+
end
|
24
|
+
|
25
|
+
def recently_completed?
|
26
|
+
return false unless changes['workflow_state']
|
27
|
+
changes['workflow_state'][1] == 'completed'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module PipelineService
|
2
|
+
module Events
|
3
|
+
module Responders
|
4
|
+
class SIS
|
5
|
+
HEADERS = { 'Content-Type' => 'application/json' }
|
6
|
+
|
7
|
+
def initialize(object:, message:, args: {})
|
8
|
+
@message = message
|
9
|
+
@args = args
|
10
|
+
configure_dependencies
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
raise 'Missing config' if missing_config?
|
15
|
+
|
16
|
+
if PipelineService.perform_synchronously?
|
17
|
+
perform
|
18
|
+
else
|
19
|
+
queue.enqueue(self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def perform
|
24
|
+
post
|
25
|
+
log
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :message, :api_key, :endpoint, :args, :queue
|
31
|
+
|
32
|
+
def configure_dependencies
|
33
|
+
@api_key = ENV['SIS_ENROLLMENT_UPDATE_API_KEY']
|
34
|
+
@endpoint = ENV['SIS_ENROLLMENT_UPDATE_ENDPOINT']
|
35
|
+
@queue = args[:queue] || Delayed::Job
|
36
|
+
end
|
37
|
+
|
38
|
+
def missing_config?
|
39
|
+
[@api_key, @endpoint].any?(&:nil?)
|
40
|
+
end
|
41
|
+
|
42
|
+
def log
|
43
|
+
PipelineService::Logger.new(
|
44
|
+
source: 'pipeline_event::graded_out',
|
45
|
+
message: message,
|
46
|
+
enpdoint: build_endpoint
|
47
|
+
).call
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_endpoint
|
51
|
+
endpoint + '?apiKey=' + api_key
|
52
|
+
end
|
53
|
+
|
54
|
+
def post
|
55
|
+
return unless message
|
56
|
+
|
57
|
+
PipelineService::Events::HTTPClient.post(
|
58
|
+
build_endpoint,
|
59
|
+
body: message.to_json,
|
60
|
+
headers: HEADERS
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|