strongmind-platform-sdk 3.19.30 → 3.19.32
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 +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +15 -12
- data/lib/platform_sdk/active_record/data_pipelineable.rb +4 -4
- data/lib/platform_sdk/canvas_api/client.rb +51 -7
- data/lib/platform_sdk/spec_support/shared_examples/data_pipelineable_examples.rb +67 -26
- data/lib/platform_sdk/spec_support/shared_examples/one_roster_data_pipelineable_examples.rb +40 -24
- data/lib/platform_sdk/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 220721704b3493784c6916cd60d381e82c6c581c0882de3afdce1bfeed4daa09
|
4
|
+
data.tar.gz: 6af5b9ec2711a28f202bcdde4f7b997633cfef6f3a0c500f272bec13d2ceb8a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eef3bdb74b5b1d66a8db6032017c379cf125b47b3f1e9cbc1ff5398fef3c2548fb87c7cafae85d0f295f96debc8c09351b29827ea4bb3e12b01fe0ab9bb868da
|
7
|
+
data.tar.gz: 6dc5553e6b644e25a0b19e598e08d260e61648a91d5dc3873fcb4d64daa26dd322875808330d76d39d62b076b2948add4204aeb9feb39678adb3ae6ea93ec1d8
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
strongmind-platform-sdk (3.19.
|
4
|
+
strongmind-platform-sdk (3.19.32)
|
5
5
|
activesupport (~> 7.1)
|
6
6
|
asset_sync
|
7
7
|
aws-sdk-secretsmanager (~> 1.66)
|
@@ -96,21 +96,21 @@ GEM
|
|
96
96
|
tzinfo (~> 2.0)
|
97
97
|
addressable (2.8.7)
|
98
98
|
public_suffix (>= 2.0.2, < 7.0)
|
99
|
-
asset_sync (2.19.
|
99
|
+
asset_sync (2.19.2)
|
100
100
|
activemodel (>= 4.1.0)
|
101
101
|
fog-core
|
102
102
|
mime-types (>= 2.99)
|
103
103
|
unf
|
104
104
|
ast (2.4.2)
|
105
105
|
aws-eventstream (1.3.0)
|
106
|
-
aws-partitions (1.
|
106
|
+
aws-partitions (1.970.0)
|
107
107
|
aws-sdk-cloudwatch (1.97.0)
|
108
108
|
aws-sdk-core (~> 3, >= 3.201.0)
|
109
109
|
aws-sigv4 (~> 1.5)
|
110
|
-
aws-sdk-core (3.
|
110
|
+
aws-sdk-core (3.202.2)
|
111
111
|
aws-eventstream (~> 1, >= 1.3.0)
|
112
112
|
aws-partitions (~> 1, >= 1.651.0)
|
113
|
-
aws-sigv4 (~> 1.
|
113
|
+
aws-sigv4 (~> 1.9)
|
114
114
|
jmespath (~> 1, >= 1.6.1)
|
115
115
|
aws-sdk-secretsmanager (1.102.0)
|
116
116
|
aws-sdk-core (~> 3, >= 3.201.0)
|
@@ -141,6 +141,8 @@ GEM
|
|
141
141
|
faraday (2.10.0)
|
142
142
|
faraday-net_http (>= 2.0, < 3.2)
|
143
143
|
logger
|
144
|
+
faraday-multipart (1.0.4)
|
145
|
+
multipart-post (~> 2)
|
144
146
|
faraday-net_http (3.1.1)
|
145
147
|
net-http
|
146
148
|
faraday-retry (2.2.1)
|
@@ -148,7 +150,8 @@ GEM
|
|
148
150
|
ffi (1.17.0)
|
149
151
|
ffi (1.17.0-x86_64-darwin)
|
150
152
|
ffi (1.17.0-x86_64-linux-gnu)
|
151
|
-
fog-aws (3.
|
153
|
+
fog-aws (3.25.0)
|
154
|
+
base64 (~> 0.2.0)
|
152
155
|
fog-core (~> 2.1)
|
153
156
|
fog-json (~> 1.1)
|
154
157
|
fog-xml (~> 0.1)
|
@@ -192,15 +195,16 @@ GEM
|
|
192
195
|
method_source (1.1.0)
|
193
196
|
mime-types (3.5.2)
|
194
197
|
mime-types-data (~> 3.2015)
|
195
|
-
mime-types-data (3.2024.
|
198
|
+
mime-types-data (3.2024.0820)
|
196
199
|
mini_mime (1.1.5)
|
197
200
|
mini_portile2 (2.8.7)
|
198
201
|
minitest (5.24.1)
|
199
202
|
multi_json (1.15.0)
|
203
|
+
multipart-post (2.4.1)
|
200
204
|
mutex_m (0.2.0)
|
201
205
|
net-http (0.4.1)
|
202
206
|
uri
|
203
|
-
net-imap (0.4.
|
207
|
+
net-imap (0.4.15)
|
204
208
|
date
|
205
209
|
net-protocol
|
206
210
|
net-pop (0.1.2)
|
@@ -314,7 +318,7 @@ GEM
|
|
314
318
|
sentry-ruby (5.18.2)
|
315
319
|
bigdecimal
|
316
320
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
317
|
-
sidekiq (7.3.
|
321
|
+
sidekiq (7.3.1)
|
318
322
|
concurrent-ruby (< 2)
|
319
323
|
connection_pool (>= 2.3.0)
|
320
324
|
logger
|
@@ -341,9 +345,7 @@ GEM
|
|
341
345
|
ethon (>= 0.9.0)
|
342
346
|
tzinfo (2.0.6)
|
343
347
|
concurrent-ruby (~> 1.0)
|
344
|
-
unf (0.
|
345
|
-
unf_ext
|
346
|
-
unf_ext (0.0.9.1)
|
348
|
+
unf (0.2.0)
|
347
349
|
unicode-display_width (2.5.0)
|
348
350
|
uri (0.13.0)
|
349
351
|
webmock (3.23.1)
|
@@ -366,6 +368,7 @@ DEPENDENCIES
|
|
366
368
|
factory_bot
|
367
369
|
faker
|
368
370
|
faraday
|
371
|
+
faraday-multipart
|
369
372
|
faraday-retry
|
370
373
|
jwt (~> 1.5, >= 1.5.4)
|
371
374
|
learnosity-sdk (~> 0.2.2)
|
@@ -9,7 +9,7 @@ module PlatformSdk
|
|
9
9
|
included do
|
10
10
|
after_create :send_pipeline_create
|
11
11
|
before_destroy :send_pipeline_destroy
|
12
|
-
after_update :send_pipeline_update
|
12
|
+
after_update :send_pipeline_update, if: -> { saved_changes? }
|
13
13
|
end
|
14
14
|
|
15
15
|
def send_pipeline_create
|
@@ -55,9 +55,9 @@ module PlatformSdk
|
|
55
55
|
|
56
56
|
def pipeline_data(action)
|
57
57
|
attributes.symbolize_keys
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
.merge(pipeline_additional_attributes)
|
59
|
+
.except(*pipeline_excluded_attributes)
|
60
|
+
.merge(action:)
|
61
61
|
end
|
62
62
|
|
63
63
|
def pipeline_additional_attributes
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "faraday"
|
4
|
+
require "faraday/multipart"
|
5
|
+
|
4
6
|
|
5
7
|
module PlatformSdk
|
6
8
|
module CanvasApiWrapper
|
7
9
|
# DataPipeline::Client
|
8
10
|
class Client
|
9
|
-
attr_reader :host, :token, :connection
|
11
|
+
attr_reader :host, :token, :connection, :multipart_connection
|
10
12
|
|
11
13
|
PAGE_SIZE = 10
|
12
14
|
|
@@ -16,6 +18,10 @@ module PlatformSdk
|
|
16
18
|
@host = "https://#{domain}"
|
17
19
|
@token = token
|
18
20
|
@connection = Faraday.new(url: host, headers: { 'Authorization' => "Bearer #{token}" })
|
21
|
+
@multipart_connection = Faraday.new(url: host, headers: { 'Authorization' => "Bearer #{token}" }) do |f|
|
22
|
+
f.request :multipart
|
23
|
+
f.adapter :net_http
|
24
|
+
end
|
19
25
|
end
|
20
26
|
|
21
27
|
# @param course_id [Integer]
|
@@ -348,6 +354,44 @@ module PlatformSdk
|
|
348
354
|
end
|
349
355
|
end
|
350
356
|
|
357
|
+
# @param course_id [Integer]
|
358
|
+
# @param module_id [Integer]
|
359
|
+
def put_relock_module(course_id:, module_id:)
|
360
|
+
uri = "api/v1/courses/#{course_id}/modules/#{module_id}/relock"
|
361
|
+
response = @connection.put(uri)
|
362
|
+
result = MultiJson.load response.body, symbolize_keys: true
|
363
|
+
handle_rate_limiting response
|
364
|
+
if success?(response.status)
|
365
|
+
result
|
366
|
+
else
|
367
|
+
error_message = parse_response_errors(result:, message: 'Error re-locking module')
|
368
|
+
raise CanvasClientError, error_message
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
# @param course_id [Integer]
|
373
|
+
# @param module_id [Integer]
|
374
|
+
# @param prerequisite_ids [Array<Integer>]
|
375
|
+
# @note prerequisite_ids should be a valid module_id, sending and empty array removes all prerequisites.
|
376
|
+
def put_module_prerequesites(course_id:, module_id:, prerequisite_ids:)
|
377
|
+
uri = "/api/v1/courses/#{course_id}/modules/#{module_id}"
|
378
|
+
response = @multipart_connection.put(uri) do |req|
|
379
|
+
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
380
|
+
req.body = { 'module[prerequisite_module_ids]' => prerequisite_ids }
|
381
|
+
end
|
382
|
+
handle_rate_limiting response
|
383
|
+
result = MultiJson.load response.body, symbolize_keys: true
|
384
|
+
if success?(response.status)
|
385
|
+
result
|
386
|
+
else
|
387
|
+
error_messages = String.new
|
388
|
+
result[:errors]&.each do |error|
|
389
|
+
error_messages << "#{error[:message]} "
|
390
|
+
end
|
391
|
+
raise "Error updating module prerequisite: #{error_messages}"
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
351
395
|
private
|
352
396
|
|
353
397
|
# @param headers [Faraday::Utils::Headers]
|
@@ -408,14 +452,14 @@ module PlatformSdk
|
|
408
452
|
# @param message [String]
|
409
453
|
def handle_post_response_errors(response, message)
|
410
454
|
result = MultiJson.load response.body, symbolize_keys: true
|
411
|
-
raise
|
412
|
-
raise
|
413
|
-
raise
|
455
|
+
raise LockedModuleItemError, "#{message}: #{result[:message]}" if response.status == 403
|
456
|
+
raise MissingModuleItemError, "#{message}: #{result[:message]}" if response.status == 404
|
457
|
+
raise CanvasClientError, parse_response_errors(message:, result:)
|
414
458
|
end
|
415
459
|
end
|
416
460
|
|
417
|
-
class
|
418
|
-
class
|
419
|
-
class
|
461
|
+
class CanvasClientError < StandardError; end
|
462
|
+
class LockedModuleItemError < CanvasClientError; end
|
463
|
+
class MissingModuleItemError < CanvasClientError; end
|
420
464
|
end
|
421
465
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module PlatformSdk
|
2
2
|
module SpecSupport
|
3
|
-
RSpec.shared_examples
|
3
|
+
RSpec.shared_examples 'DataPipelineable' do |params = {}|
|
4
|
+
let(:shared_params) { params }
|
4
5
|
let(:record) { build(described_class.to_s.underscore.to_sym) }
|
5
6
|
let(:data_pipeline_client) { double(PlatformSdk::DataPipeline::Client) }
|
6
7
|
|
@@ -12,41 +13,76 @@ module PlatformSdk
|
|
12
13
|
|
13
14
|
after { Timecop.return }
|
14
15
|
|
15
|
-
context '
|
16
|
-
before { allow(record).to receive(:send_pipeline_create) }
|
16
|
+
context 'create' do
|
17
17
|
|
18
|
-
it
|
18
|
+
it 'posts a copy of our data to the data pipeline' do
|
19
19
|
record.save!
|
20
|
-
expect(
|
20
|
+
expect(data_pipeline_client).to have_received(:post).once.with(hash_including(
|
21
|
+
noun: described_class.pipeline_noun,
|
22
|
+
data: record.pipeline_data('created'),
|
23
|
+
meta: described_class.pipeline_meta,
|
24
|
+
envelope_version: '1.0.0',
|
25
|
+
message_timestamp: Time.current.utc.iso8601,
|
26
|
+
identifiers: { id: record.id },
|
27
|
+
))
|
21
28
|
end
|
22
29
|
end
|
23
30
|
|
24
|
-
context '
|
25
|
-
before do
|
26
|
-
allow(record).to receive(:send_pipeline_create)
|
27
|
-
allow(record).to receive(:send_pipeline_destroy)
|
28
|
-
end
|
31
|
+
context 'destroy' do
|
29
32
|
|
30
|
-
it
|
33
|
+
it 'posts a copy of our data to the data pipeline' do
|
31
34
|
record.save!
|
32
35
|
record.destroy
|
33
36
|
|
34
|
-
expect(
|
35
|
-
|
37
|
+
expect(data_pipeline_client).to have_received(:post).once.with(hash_including(
|
38
|
+
noun: described_class.pipeline_noun,
|
39
|
+
data: record.pipeline_data('destroyed'),
|
40
|
+
meta: described_class.pipeline_meta,
|
41
|
+
envelope_version: '1.0.0',
|
42
|
+
message_timestamp: Time.current.utc.iso8601,
|
43
|
+
identifiers: { id: record.id }
|
44
|
+
))
|
36
45
|
end
|
37
46
|
end
|
38
47
|
|
39
|
-
context '
|
48
|
+
context 'update' do
|
40
49
|
before do
|
41
|
-
allow(record).to receive(:send_pipeline_update)
|
42
50
|
record.save!
|
43
51
|
end
|
44
52
|
|
45
|
-
|
46
|
-
|
47
|
-
|
53
|
+
context 'after an update' do
|
54
|
+
it 'posts a copy of our data to the data pipeline' do
|
55
|
+
column = column_to_update(record)
|
56
|
+
unless column.nil?
|
57
|
+
update_record(record, column)
|
58
|
+
expect(data_pipeline_client).to have_received(:post).once.with(hash_including(
|
59
|
+
noun: described_class.pipeline_noun,
|
60
|
+
data: record.pipeline_data('modified'),
|
61
|
+
meta: described_class.pipeline_meta,
|
62
|
+
envelope_version: '1.0.0',
|
63
|
+
message_timestamp: Time.current.utc.iso8601,
|
64
|
+
identifiers: { id: record.id },
|
65
|
+
))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'after an update that does not change data' do
|
71
|
+
let(:action) { 'modified' }
|
72
|
+
|
73
|
+
it 'does not post to the data pipeline' do
|
74
|
+
record.save!
|
75
|
+
column = column_to_update(record)
|
48
76
|
update_record(record, column)
|
49
|
-
|
77
|
+
record.save!
|
78
|
+
expect(data_pipeline_client).to have_received(:post).once.with(hash_including(
|
79
|
+
noun: described_class.pipeline_noun,
|
80
|
+
data: record.pipeline_data('modified'),
|
81
|
+
meta: described_class.pipeline_meta,
|
82
|
+
envelope_version: '1.0.0',
|
83
|
+
message_timestamp: Time.current.utc.iso8601,
|
84
|
+
identifiers: { id: record.id }
|
85
|
+
))
|
50
86
|
end
|
51
87
|
end
|
52
88
|
end
|
@@ -60,13 +96,18 @@ module PlatformSdk
|
|
60
96
|
end
|
61
97
|
|
62
98
|
def update_record(record, column)
|
99
|
+
if shared_params[:update_method]
|
100
|
+
send(shared_params[:update_method], record)
|
101
|
+
return
|
102
|
+
end
|
63
103
|
new_record = build(described_class.to_s.underscore.to_sym)
|
64
104
|
|
65
|
-
|
66
|
-
|
67
|
-
|
105
|
+
column_value = record[column]
|
106
|
+
column_class = column_value.class
|
107
|
+
case column_class.to_s
|
108
|
+
when 'Integer'
|
68
109
|
record.update!(column => 69)
|
69
|
-
when
|
110
|
+
when 'TrueClass', 'FalseClass'
|
70
111
|
record.update!(column => !record[column])
|
71
112
|
else
|
72
113
|
record.update!(column => new_record[column])
|
@@ -90,9 +131,9 @@ module PlatformSdk
|
|
90
131
|
|
91
132
|
let(:data) do
|
92
133
|
record.attributes.symbolize_keys
|
93
|
-
|
94
|
-
|
95
|
-
|
134
|
+
.merge(record.pipeline_additional_attributes)
|
135
|
+
.except(*record.pipeline_excluded_attributes)
|
136
|
+
.merge(action:)
|
96
137
|
end
|
97
138
|
|
98
139
|
it 'returns the expected payload' do
|
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
module PlatformSdk
|
4
4
|
module SpecSupport
|
5
|
-
RSpec.shared_examples
|
5
|
+
RSpec.shared_examples 'OneRosterDataPipelineable' do |record_keys|
|
6
6
|
let(:data) do
|
7
7
|
record_keys.each_with_object({}) do |key, hash|
|
8
|
-
if key ==
|
8
|
+
if key == '_type'
|
9
9
|
hash[:type] = record.try(key)
|
10
10
|
next
|
11
11
|
end
|
@@ -17,11 +17,11 @@ module PlatformSdk
|
|
17
17
|
"noun": "StrongMind.Platform.OneRoster.#{record.one_roster_data_type.capitalize}",
|
18
18
|
"identifiers": { "sourcedId": record.roster_id },
|
19
19
|
"meta": {
|
20
|
-
"version":
|
21
|
-
"source":
|
20
|
+
"version": '3',
|
21
|
+
"source": 'Central OneRoster API'
|
22
22
|
},
|
23
23
|
"data": one_roster_common_data.merge!(data),
|
24
|
-
"envelope_version":
|
24
|
+
"envelope_version": '1.0.0',
|
25
25
|
"message_timestamp": Time.current.utc.iso8601
|
26
26
|
}
|
27
27
|
end
|
@@ -39,27 +39,27 @@ module PlatformSdk
|
|
39
39
|
before do
|
40
40
|
allow(PlatformSdk::DataPipeline::Client).to receive(:new).and_return(data_pipeline_client)
|
41
41
|
allow(data_pipeline_client).to receive(:post)
|
42
|
-
Timecop.freeze(DateTime.parse(
|
42
|
+
Timecop.freeze(DateTime.parse('2024-06-09 04:20:00'))
|
43
43
|
end
|
44
44
|
|
45
45
|
after do
|
46
46
|
Timecop.return
|
47
47
|
end
|
48
48
|
|
49
|
-
context
|
50
|
-
let(:action) {
|
49
|
+
context 'create' do
|
50
|
+
let(:action) { 'created' }
|
51
51
|
|
52
|
-
it
|
52
|
+
it 'posts to the data pipeline' do
|
53
53
|
record.save!
|
54
54
|
expect(data_pipeline_client).to have_received(:post).with(pipeline_payload)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
context
|
59
|
-
let(:action) {
|
58
|
+
context 'destroy' do
|
59
|
+
let(:action) { 'destroyed' }
|
60
60
|
let(:one_roster_common_data_deleted) do
|
61
61
|
one_roster_common_data.merge(
|
62
|
-
"status":
|
62
|
+
"status": 'tobedeleted'
|
63
63
|
)
|
64
64
|
end
|
65
65
|
|
@@ -67,7 +67,7 @@ module PlatformSdk
|
|
67
67
|
pipeline_payload.merge("data": one_roster_common_data_deleted)
|
68
68
|
end
|
69
69
|
|
70
|
-
it
|
70
|
+
it 'posts to the data pipeline' do
|
71
71
|
record.save!
|
72
72
|
record.destroy
|
73
73
|
expect(data_pipeline_client).to have_received(:post).with(pipeline_payload)
|
@@ -75,18 +75,33 @@ module PlatformSdk
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
-
context
|
79
|
-
let(:action) {
|
78
|
+
context 'update' do
|
79
|
+
let(:action) { 'modified' }
|
80
80
|
|
81
81
|
before do
|
82
|
-
allow(record).to receive(:send_pipeline_create)
|
83
82
|
record.save!
|
84
83
|
end
|
85
84
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
85
|
+
context 'after an update' do
|
86
|
+
|
87
|
+
it 'posts a copy of our data to the data pipeline' do
|
88
|
+
column = column_to_update(record)
|
89
|
+
update_record(record, column)
|
90
|
+
expect(data_pipeline_client).to have_received(:post).with(pipeline_payload)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'after an update that does not change data' do
|
96
|
+
let(:action) { 'modified' }
|
97
|
+
|
98
|
+
it 'does not post to the data pipeline' do
|
99
|
+
record.save!
|
100
|
+
column = column_to_update(record)
|
101
|
+
update_record(record, column)
|
102
|
+
record.save!
|
103
|
+
expect(data_pipeline_client).to have_received(:post).once.with(pipeline_payload)
|
104
|
+
end
|
90
105
|
end
|
91
106
|
end
|
92
107
|
|
@@ -100,11 +115,12 @@ module PlatformSdk
|
|
100
115
|
def update_record(record, column)
|
101
116
|
new_record = build(described_class.to_s.underscore.to_sym)
|
102
117
|
|
103
|
-
|
104
|
-
|
105
|
-
|
118
|
+
column_value = record[column]
|
119
|
+
column_class = column_value.class
|
120
|
+
case column_class.to_s
|
121
|
+
when 'Integer'
|
106
122
|
record.update!(column => 69)
|
107
|
-
when
|
123
|
+
when 'TrueClass', 'FalseClass'
|
108
124
|
record.update!(column => !record[column])
|
109
125
|
else
|
110
126
|
record.update!(column => new_record[column])
|
data/lib/platform_sdk/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strongmind-platform-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.19.
|
4
|
+
version: 3.19.32
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Platform Team
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|