canvas_shim 0.1.6 → 0.1.7rc
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 +5 -5
- data/app/services/pipeline_service/commands/publish.rb +1 -3
- data/app/services/pipeline_service/endpoints/pipeline/message_builder.rb +2 -3
- data/app/services/pipeline_service/events/emitter.rb +38 -16
- data/app/services/pipeline_service/events/grade_changed_event.rb +24 -0
- data/app/services/pipeline_service/events/responders/sis_unit_grade.rb +61 -0
- data/app/services/pipeline_service/http_client.rb +2 -1
- data/app/services/pipeline_service/nouns/unit_grades.rb +13 -0
- data/app/services/pipeline_service/serializers/fetcher.rb +2 -0
- data/app/services/pipeline_service/serializers/unit_grades.rb +17 -0
- data/app/services/units_service/commands/get_unit_grades.rb +43 -0
- data/app/services/units_service/grades_calculator.rb +39 -0
- data/app/services/units_service/queries/get_category_weights.rb +13 -0
- data/app/services/units_service/queries/get_items.rb +24 -0
- data/app/services/units_service/queries/get_submissions.rb +25 -0
- data/lib/canvas_shim/engine.rb +2 -3
- data/lib/canvas_shim/version.rb +1 -1
- metadata +17 -64
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b5d833c2d89b8226175ffb44f45af903d1ec442e841449b8fe98c113b4c0e411
|
4
|
+
data.tar.gz: 26de1db9614e6743aff4a6ecdeaae65c434cbca86c5084ab0acfe63419c1533e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d0eec50ee26d4e9a2ebbc20552f5417079054bf43f2a42c3f3d97d67fd9716077d2897d82b6c93ac5fe29af5e8187cc49ab8cc7ea1592abf5036d7f6c946d9f
|
7
|
+
data.tar.gz: 6bafe2b3171ff7018c5c390619fc36bfa8cf5eeb77f25783dc68dcff31bd6a7647c6a764d418a30c81cd644de83264c839415aa831102ee1e6d9806ac2b2a132
|
@@ -46,7 +46,6 @@ module PipelineService
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def configure_dependencies
|
49
|
-
@message_class = @args[:message_class] || PipelinePublisher::Message
|
50
49
|
@fetcher = @args[:fetcher] || Serializers::Fetcher
|
51
50
|
@logger = @args[:logger] || PipelineService::Logger
|
52
51
|
@canvas_domain = ENV['CANVAS_DOMAIN']
|
@@ -58,11 +57,11 @@ module PipelineService
|
|
58
57
|
end
|
59
58
|
|
60
59
|
def build
|
61
|
-
|
60
|
+
payload.to_hash
|
62
61
|
end
|
63
62
|
|
64
63
|
def noun
|
65
|
-
object.class.to_s.underscore
|
64
|
+
object.class.to_s.split('::').last.underscore
|
66
65
|
end
|
67
66
|
|
68
67
|
def id
|
@@ -1,16 +1,13 @@
|
|
1
1
|
module PipelineService
|
2
2
|
module Events
|
3
3
|
class Emitter
|
4
|
-
def initialize
|
4
|
+
def initialize args={}
|
5
5
|
@object = args[:object]
|
6
6
|
@args = args
|
7
|
-
@responder
|
8
|
-
@event = Events::GradedOutEvent
|
7
|
+
@responder = @args[:responder] || Events::Responders::SIS
|
9
8
|
end
|
10
9
|
|
11
10
|
def call
|
12
|
-
return unless object.is_a?(Enrollment)
|
13
|
-
fetch_serializer
|
14
11
|
build_message
|
15
12
|
build_responder
|
16
13
|
build_subscriptions
|
@@ -19,26 +16,49 @@ module PipelineService
|
|
19
16
|
|
20
17
|
private
|
21
18
|
|
22
|
-
attr_reader :subscriptions, :object, :responder, :message
|
19
|
+
attr_reader :subscriptions, :object, :responder, :message
|
20
|
+
|
21
|
+
def events
|
22
|
+
{
|
23
|
+
graded_out: Events::GradedOutEvent,
|
24
|
+
grade_changed: Events::GradeChangedEvent
|
25
|
+
}
|
26
|
+
end
|
23
27
|
|
24
28
|
def build_subscriptions
|
25
|
-
@subscriptions = [
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
+
@subscriptions = []
|
30
|
+
|
31
|
+
@subscriptions << Events::Subscription.new(
|
32
|
+
event: :graded_out,
|
33
|
+
responder: Events::Responders::SIS.new(
|
34
|
+
object: object,
|
35
|
+
message: message
|
36
|
+
)
|
37
|
+
)
|
38
|
+
|
39
|
+
@subscriptions << Events::Subscription.new(
|
40
|
+
event: :grade_changed,
|
41
|
+
responder: Events::Responders::SISUnitGrade.new(
|
42
|
+
object: object,
|
43
|
+
message: message
|
29
44
|
)
|
30
|
-
|
45
|
+
)
|
31
46
|
end
|
32
47
|
|
33
48
|
def build_responder
|
34
|
-
@responder =
|
49
|
+
@responder = Events::Responders::SIS.new(
|
35
50
|
object: object,
|
36
51
|
message: message
|
37
52
|
)
|
38
53
|
end
|
39
54
|
|
40
|
-
def
|
41
|
-
|
55
|
+
def serializer
|
56
|
+
case object
|
57
|
+
when PipelineService::Nouns::UnitGrades
|
58
|
+
Serializers::UnitGrades
|
59
|
+
when Enrollment
|
60
|
+
Serializers::CanvasAPIEnrollment
|
61
|
+
end
|
42
62
|
end
|
43
63
|
|
44
64
|
def build_message
|
@@ -47,8 +67,10 @@ module PipelineService
|
|
47
67
|
|
48
68
|
def emit
|
49
69
|
subscriptions.each do |subscription|
|
50
|
-
next
|
51
|
-
event.new(
|
70
|
+
next unless events[subscription.event]
|
71
|
+
events[subscription.event].new(
|
72
|
+
@args.merge(subscription: subscription)
|
73
|
+
).emit
|
52
74
|
end
|
53
75
|
end
|
54
76
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module PipelineService
|
2
|
+
module Events
|
3
|
+
class GradeChangedEvent
|
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
|
+
def should_trigger?
|
19
|
+
return false unless @changes['score']
|
20
|
+
@object.is_a?(PipelineService::Nouns::UnitGrades)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module PipelineService
|
2
|
+
module Events
|
3
|
+
module Responders
|
4
|
+
class SISUnitGrade
|
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
|
+
queue.enqueue(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def perform
|
19
|
+
post
|
20
|
+
log
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :message, :api_key, :endpoint, :args, :queue
|
26
|
+
|
27
|
+
def configure_dependencies
|
28
|
+
@api_key = ENV['SIS_UNIT_GRADE_ENDPOINT_API_KEY']
|
29
|
+
@endpoint = ENV['SIS_UNIT_GRADE_ENDPOINT']
|
30
|
+
@queue = args[:queue] || Delayed::Job
|
31
|
+
end
|
32
|
+
|
33
|
+
def missing_config?
|
34
|
+
[@api_key, @endpoint].any?(&:nil?)
|
35
|
+
end
|
36
|
+
|
37
|
+
def log
|
38
|
+
PipelineService::Logger.new(
|
39
|
+
source: 'pipeline_event::grade_changed',
|
40
|
+
message: message,
|
41
|
+
enpdoint: build_endpoint
|
42
|
+
).call
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_endpoint
|
46
|
+
endpoint + '?apiKey=' + api_key
|
47
|
+
end
|
48
|
+
|
49
|
+
def post
|
50
|
+
return unless message
|
51
|
+
|
52
|
+
PipelineService::Events::HTTPClient.post(
|
53
|
+
build_endpoint,
|
54
|
+
body: message.to_json,
|
55
|
+
headers: HEADERS
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -11,7 +11,8 @@ module PipelineService
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def post(args)
|
14
|
-
PipelinePublisher::
|
14
|
+
message = PipelinePublisher::Message.new(*args)
|
15
|
+
PipelinePublisher::MessagesApi.new.messages_post(message)
|
15
16
|
end
|
16
17
|
|
17
18
|
def get(endpoint, args)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module PipelineService
|
2
|
+
module Nouns
|
3
|
+
class UnitGrades
|
4
|
+
attr_reader :course, :student, :id, :changes
|
5
|
+
def initialize(submission)
|
6
|
+
@course = submission.assignment.course
|
7
|
+
@student = submission.user
|
8
|
+
@id = submission.id
|
9
|
+
@changes = submission.changes
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -6,6 +6,8 @@ module PipelineService
|
|
6
6
|
# Maps any object with a class containing 'enrollment' to the enrollment serializer
|
7
7
|
def self.fetch(object:)
|
8
8
|
case object.class.to_s
|
9
|
+
when /PipelineService::Nouns::UnitGrades/
|
10
|
+
PipelineService::Serializers::UnitGrades
|
9
11
|
when /Enrollment/
|
10
12
|
PipelineService::Serializers::Enrollment
|
11
13
|
else
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module PipelineService
|
2
|
+
module Serializers
|
3
|
+
class UnitGrades
|
4
|
+
def initialize(object:)
|
5
|
+
@course = object.course
|
6
|
+
@student = object.student
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
UnitsService::Commands::GetUnitGrades.new(
|
11
|
+
course: @course,
|
12
|
+
student: @student
|
13
|
+
).call
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module UnitsService
|
2
|
+
module Commands
|
3
|
+
class GetUnitGrades
|
4
|
+
def initialize(course:, student:)
|
5
|
+
@course = course
|
6
|
+
@student = student
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
get_submissions
|
11
|
+
calculate_grades
|
12
|
+
payload
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def get_submissions
|
18
|
+
@unit_submissions = UnitsService::Queries::GetSubmissions.new(
|
19
|
+
course: @course,
|
20
|
+
student: @student
|
21
|
+
).query
|
22
|
+
end
|
23
|
+
|
24
|
+
def payload
|
25
|
+
{
|
26
|
+
school_domain: ENV['CANVAS_DOMAIN'],
|
27
|
+
course_id: @course.id,
|
28
|
+
student_id: @student.id,
|
29
|
+
units: @grades.map {|unit, score| {
|
30
|
+
id: unit.id,
|
31
|
+
position: unit.position,
|
32
|
+
created_at: unit.created_at,
|
33
|
+
score: score
|
34
|
+
}}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def calculate_grades
|
39
|
+
@grades = UnitsService::GradesCalculator.new(@unit_submissions, @course).call
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module UnitsService
|
2
|
+
class GradesCalculator
|
3
|
+
def initialize unit_submissions, course
|
4
|
+
@unit_submissions = unit_submissions
|
5
|
+
@course = course
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
result = {}
|
10
|
+
|
11
|
+
@unit_submissions.each do |unit, submissions|
|
12
|
+
next if submissions.count == 0
|
13
|
+
result[unit] = weighted_average(submissions)
|
14
|
+
end
|
15
|
+
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def weighted_average(submissions)
|
22
|
+
result = {}
|
23
|
+
submissions.group_by do |submission|
|
24
|
+
submission.assignment.assignment_group
|
25
|
+
end.each do |group, submissions|
|
26
|
+
result[group.name] = [] unless result[group]
|
27
|
+
average = submissions.sum(&:score).to_f / submissions.count
|
28
|
+
weight = category_weights[group.name] / category_weights.values.sum
|
29
|
+
result[group.name] << average * weight
|
30
|
+
end
|
31
|
+
|
32
|
+
result.sum { |r, weighted| weighted.sum }
|
33
|
+
end
|
34
|
+
|
35
|
+
def category_weights
|
36
|
+
Queries::GetCategoryWeights.new(@course).query
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module UnitsService
|
2
|
+
module Queries
|
3
|
+
class GetItems
|
4
|
+
def initialize(course:)
|
5
|
+
@course = course
|
6
|
+
@context_modules = @course.context_modules
|
7
|
+
end
|
8
|
+
|
9
|
+
def query
|
10
|
+
result = {}
|
11
|
+
|
12
|
+
@context_modules.each do |context_module|
|
13
|
+
result[context_module] =
|
14
|
+
context_module.content_tags.select do |ct|
|
15
|
+
ct.content.present? &&
|
16
|
+
ct.content.respond_to?(:submissions) &&
|
17
|
+
ct.content.workflow_state == 'published'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module UnitsService
|
2
|
+
module Queries
|
3
|
+
class GetSubmissions
|
4
|
+
def initialize(course:, student:)
|
5
|
+
@student = student
|
6
|
+
@course = course
|
7
|
+
end
|
8
|
+
|
9
|
+
def query
|
10
|
+
result = {}
|
11
|
+
units.each do |unit, items|
|
12
|
+
items.each do |item|
|
13
|
+
result[unit] = item.content.submissions.where(user_id: @student.id).where("excused is not true")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
def units
|
21
|
+
GetItems.new(course: @course).query
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/canvas_shim/engine.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
require 'csv'
|
2
|
-
require "rails"
|
3
2
|
require 'grape'
|
4
3
|
require 'grape-swagger'
|
5
4
|
require 'grape-swagger-ui'
|
6
5
|
require 'aws-sdk-dynamodb'
|
7
|
-
require "pipeline_publisher_ruby"
|
8
6
|
require "business"
|
9
7
|
require 'httparty'
|
10
8
|
require 'aws-sdk-core'
|
11
9
|
require 'aws-sdk-s3'
|
12
|
-
require 'inst-jobs'
|
13
10
|
require 'pg'
|
11
|
+
require "pipeline_publisher_ruby"
|
12
|
+
|
14
13
|
|
15
14
|
module CanvasShim
|
16
15
|
class Engine < ::Rails::Engine
|
data/lib/canvas_shim/version.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: canvas_shim
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7rc
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ''
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-11-
|
11
|
+
date: 2018-11-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: rails
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 5.0.4
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 5.0.4
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: grape
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -123,61 +109,19 @@ dependencies:
|
|
123
109
|
- !ruby/object:Gem::Version
|
124
110
|
version: '0'
|
125
111
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
112
|
+
name: loofah
|
127
113
|
requirement: !ruby/object:Gem::Requirement
|
128
114
|
requirements:
|
129
115
|
- - '='
|
130
116
|
- !ruby/object:Gem::Version
|
131
|
-
version:
|
117
|
+
version: 2.2.3
|
132
118
|
type: :runtime
|
133
119
|
prerelease: false
|
134
120
|
version_requirements: !ruby/object:Gem::Requirement
|
135
121
|
requirements:
|
136
122
|
- - '='
|
137
123
|
- !ruby/object:Gem::Version
|
138
|
-
version:
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: aws-sdk-s3
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - ">="
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
type: :runtime
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - ">="
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0'
|
153
|
-
- !ruby/object:Gem::Dependency
|
154
|
-
name: inst-jobs
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
156
|
-
requirements:
|
157
|
-
- - ">="
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: '0'
|
160
|
-
type: :runtime
|
161
|
-
prerelease: false
|
162
|
-
version_requirements: !ruby/object:Gem::Requirement
|
163
|
-
requirements:
|
164
|
-
- - ">="
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: '0'
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: pg
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - ">="
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
174
|
-
type: :runtime
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - ">="
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0'
|
124
|
+
version: 2.2.3
|
181
125
|
description: A rails engine to be integrated with Canvas
|
182
126
|
email:
|
183
127
|
- ''
|
@@ -215,12 +159,15 @@ files:
|
|
215
159
|
- app/services/pipeline_service/endpoints/pipeline.rb
|
216
160
|
- app/services/pipeline_service/endpoints/pipeline/message_builder.rb
|
217
161
|
- app/services/pipeline_service/events/emitter.rb
|
162
|
+
- app/services/pipeline_service/events/grade_changed_event.rb
|
218
163
|
- app/services/pipeline_service/events/graded_out_event.rb
|
219
164
|
- app/services/pipeline_service/events/http_client.rb
|
220
165
|
- app/services/pipeline_service/events/responders/sis.rb
|
166
|
+
- app/services/pipeline_service/events/responders/sis_unit_grade.rb
|
221
167
|
- app/services/pipeline_service/events/subscription.rb
|
222
168
|
- app/services/pipeline_service/http_client.rb
|
223
169
|
- app/services/pipeline_service/logger.rb
|
170
|
+
- app/services/pipeline_service/nouns/unit_grades.rb
|
224
171
|
- app/services/pipeline_service/pipeline_client.rb
|
225
172
|
- app/services/pipeline_service/serializers/assignment.rb
|
226
173
|
- app/services/pipeline_service/serializers/base_methods.rb
|
@@ -228,6 +175,7 @@ files:
|
|
228
175
|
- app/services/pipeline_service/serializers/enrollment.rb
|
229
176
|
- app/services/pipeline_service/serializers/fetcher.rb
|
230
177
|
- app/services/pipeline_service/serializers/submission.rb
|
178
|
+
- app/services/pipeline_service/serializers/unit_grades.rb
|
231
179
|
- app/services/pipeline_service/serializers/user.rb
|
232
180
|
- app/services/settings_service.rb
|
233
181
|
- app/services/settings_service/api_docs.yml
|
@@ -250,6 +198,11 @@ files:
|
|
250
198
|
- app/services/settings_service/student_assignment_repository.rb
|
251
199
|
- app/services/settings_service/submission.rb
|
252
200
|
- app/services/settings_service/user.rb
|
201
|
+
- app/services/units_service/commands/get_unit_grades.rb
|
202
|
+
- app/services/units_service/grades_calculator.rb
|
203
|
+
- app/services/units_service/queries/get_category_weights.rb
|
204
|
+
- app/services/units_service/queries/get_items.rb
|
205
|
+
- app/services/units_service/queries/get_submissions.rb
|
253
206
|
- app/views/layouts/canvas_shim/application.html.erb
|
254
207
|
- config/initializers/hash.rb
|
255
208
|
- config/initializers/string.rb
|
@@ -273,12 +226,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
273
226
|
version: '0'
|
274
227
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
275
228
|
requirements:
|
276
|
-
- - "
|
229
|
+
- - ">"
|
277
230
|
- !ruby/object:Gem::Version
|
278
|
-
version:
|
231
|
+
version: 1.3.1
|
279
232
|
requirements: []
|
280
233
|
rubyforge_project:
|
281
|
-
rubygems_version: 2.
|
234
|
+
rubygems_version: 2.7.7
|
282
235
|
signing_key:
|
283
236
|
specification_version: 4
|
284
237
|
summary: A tool to make it easy to write code that plugs into canvas
|