canvas_link_migrator 0.1.1 → 0.2.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 +4 -4
- data/lib/canvas_link_migrator/link_resolver.rb +16 -11
- data/lib/canvas_link_migrator/resource_map_service.rb +8 -3
- data/spec/canvas_link_migrator/imported_html_converter_spec.rb +38 -45
- data/spec/canvas_link_migrator/link_resolver_spec.rb +12 -0
- data/spec/fixtures/canvas_resource_map.json +27 -7
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 989d0e7044ece5d052b909fa121de7f205906c733908a8dcf4a26d619cf67caf
|
4
|
+
data.tar.gz: a065d7f5ad6ad400cbd7cf448f5d42c5b69d400d9123837d2517950059d4a70e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50b673e7a91c0f345455c0ef0027b9e05a84c973f35d75f5659d336dfb70a3385d04d3360bead41b16a5101e3f8ee5e054f881cab88ac3f7c3ec0d5387cb7c6c
|
7
|
+
data.tar.gz: 8519365222facd8c05af020e7c6734db5c61e386cfd1fd0097f1a0815924c2c622929c217bfa4772d37fcd3acadd118019672cd9958ad76951512acd4bd8c4ba
|
@@ -18,9 +18,14 @@
|
|
18
18
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
19
|
|
20
20
|
require "active_support/core_ext/object"
|
21
|
+
require "rack"
|
21
22
|
|
22
23
|
module CanvasLinkMigrator
|
23
24
|
class LinkResolver
|
25
|
+
attr_accessor :migration_id_converter
|
26
|
+
|
27
|
+
delegate :context_path, :attachment_path_id_lookup, to: :migration_id_converter
|
28
|
+
|
24
29
|
def initialize(migration_id_converter)
|
25
30
|
@migration_id_converter = migration_id_converter
|
26
31
|
end
|
@@ -35,8 +40,8 @@ module CanvasLinkMigrator
|
|
35
40
|
end
|
36
41
|
end
|
37
42
|
|
38
|
-
def
|
39
|
-
@
|
43
|
+
def attachment_path_id_lookup_lower
|
44
|
+
@attachment_path_id_lookup_lower ||= attachment_path_id_lookup&.transform_keys(&:downcase)
|
40
45
|
end
|
41
46
|
|
42
47
|
# finds the :new_value to use to replace the placeholder
|
@@ -150,13 +155,13 @@ module CanvasLinkMigrator
|
|
150
155
|
# This is for backward-compatibility: canvas attachment filenames are escaped
|
151
156
|
# with '+' for spaces and older exports have files with that instead of %20
|
152
157
|
alt_rel_path = rel_path.tr("+", " ")
|
153
|
-
if
|
154
|
-
mig_id ||=
|
155
|
-
mig_id ||=
|
158
|
+
if attachment_path_id_lookup
|
159
|
+
mig_id ||= attachment_path_id_lookup[rel_path]
|
160
|
+
mig_id ||= attachment_path_id_lookup[alt_rel_path]
|
156
161
|
end
|
157
|
-
if !mig_id &&
|
158
|
-
mig_id ||=
|
159
|
-
mig_id ||=
|
162
|
+
if !mig_id && attachment_path_id_lookup_lower
|
163
|
+
mig_id ||= attachment_path_id_lookup_lower[rel_path.downcase]
|
164
|
+
mig_id ||= attachment_path_id_lookup_lower[alt_rel_path.downcase]
|
160
165
|
end
|
161
166
|
|
162
167
|
# This md5 comparison is here to handle faulty cartridges with the migration_id equivalent of an empty string
|
@@ -182,7 +187,7 @@ module CanvasLinkMigrator
|
|
182
187
|
while new_url.nil? && !rel_path_parts.empty?
|
183
188
|
sub_path = File.join(rel_path_parts)
|
184
189
|
if (file = find_file_in_context(sub_path))
|
185
|
-
new_url = "#{context_path}/files/#{file
|
190
|
+
new_url = "#{context_path}/files/#{file["id"]}"
|
186
191
|
# support other params in the query string, that were exported from the
|
187
192
|
# original path components and query string. see
|
188
193
|
# CCHelper::file_query_string
|
@@ -219,13 +224,13 @@ module CanvasLinkMigrator
|
|
219
224
|
|
220
225
|
def resolve_media_comment_data(node, rel_path)
|
221
226
|
if (file = find_file_in_context(rel_path[/^[^?]+/])) # strip query string for this search
|
222
|
-
media_id =
|
227
|
+
media_id = file.try(:media_object)&.media_id || file["media_entry_id"]
|
223
228
|
if media_id && media_id != "maybe"
|
224
229
|
if ["iframe", "source"].include?(node.name)
|
225
230
|
node["data-media-id"] = media_id
|
226
231
|
if node["data-is-media-attachment"]
|
227
232
|
node.delete("data-is-media-attachment")
|
228
|
-
return media_attachment_iframe_url(file
|
233
|
+
return media_attachment_iframe_url(file["id"], node["data-media-type"])
|
229
234
|
else
|
230
235
|
return media_iframe_url(media_id, node["data-media-type"])
|
231
236
|
end
|
@@ -52,9 +52,9 @@ module CanvasLinkMigrator
|
|
52
52
|
migration_data["destination_hosts"]
|
53
53
|
end
|
54
54
|
|
55
|
-
def attachment_path_id_lookup
|
56
|
-
|
57
|
-
|
55
|
+
def attachment_path_id_lookup
|
56
|
+
migration_data["attachment_path_id_lookup"]
|
57
|
+
end
|
58
58
|
|
59
59
|
def root_folder_name
|
60
60
|
migration_data["destination_root_folder"]
|
@@ -93,6 +93,7 @@ module CanvasLinkMigrator
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def convert_migration_id(type, migration_id)
|
96
|
+
type = "modules" if type == "context_modules"
|
96
97
|
id = if CanvasLinkMigrator::LinkParser::KNOWN_REFERENCE_TYPES.include? type
|
97
98
|
resources.dig(type, migration_id, "destination", "id")
|
98
99
|
end
|
@@ -101,5 +102,9 @@ module CanvasLinkMigrator
|
|
101
102
|
# we'll check both here
|
102
103
|
convert_announcement_migration_id(migration_id) if type == "discussion_topics"
|
103
104
|
end
|
105
|
+
|
106
|
+
def lookup_attachment_by_migration_id(migration_id)
|
107
|
+
resources.dig("files", migration_id, "destination")
|
108
|
+
end
|
104
109
|
end
|
105
110
|
end
|
@@ -56,62 +56,55 @@ describe CanvasLinkMigrator::ImportedHtmlConverter do
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
# it "finds an attachment by path" do
|
65
|
-
# test_string = %{<p>This is an image: <br /><img src="%24IMS_CC_FILEBASE%24/test.png" alt=":(" /></p>}
|
66
|
-
|
67
|
-
# # if there isn't a path->migration id map it'll be a relative course file path
|
68
|
-
# expect(@converter.convert_exported_html(test_string)).to eq %{<p>This is an image: <br><img src="#{@path}file_contents/course%20files/test.png" alt=":("></p>}
|
69
|
-
|
70
|
-
# @migration.attachment_path_id_lookup = { "test.png" => att.migration_id }
|
71
|
-
# expect(@converter.convert_exported_html(test_string)).to eq %{<p>This is an image: <br><img src="#{@path}files/#{att.id}/preview" alt=":("></p>}
|
72
|
-
# end
|
59
|
+
it "finds an attachment by migration id" do
|
60
|
+
test_string = %{<p>This is an image: <br /><img src="%24CANVAS_OBJECT_REFERENCE%24/attachments/F" alt=":(" /></p>}
|
61
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%{<p>This is an image: <br><img src="#{@path}files/6/preview" alt=":("></p>}, nil])
|
62
|
+
end
|
73
63
|
|
74
|
-
|
75
|
-
|
76
|
-
# @migration.attachment_path_id_lookup = { "subfolder/with a space/test.png" => att.migration_id }
|
64
|
+
it "finds an attachment by path" do
|
65
|
+
test_string = %{<p>This is an image: <br /><img src="%24IMS_CC_FILEBASE%24/test.png" alt=":(" /></p>}
|
77
66
|
|
78
|
-
|
79
|
-
|
67
|
+
# if there isn't a path->migration id map it'll be a relative course file path
|
68
|
+
expect(@converter.link_resolver).to receive(:attachment_path_id_lookup).exactly(4).times.and_return({})
|
69
|
+
html, bad_links = @converter.convert_exported_html(test_string)
|
70
|
+
expect(html).to eq %{<p>This is an image: <br><img src="#{@path}file_contents/course%20files/test.png" alt=":("></p>}
|
71
|
+
expect(bad_links[0]).to include({ link_type: :file, missing_url: "/courses/2/file_contents/course%20files/test.png" })
|
80
72
|
|
81
|
-
|
82
|
-
|
83
|
-
|
73
|
+
expect(@converter.link_resolver).to receive(:attachment_path_id_lookup).twice.and_call_original
|
74
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%{<p>This is an image: <br><img src="#{@path}files/5/preview" alt=":("></p>}, nil])
|
75
|
+
end
|
84
76
|
|
85
|
-
|
86
|
-
|
87
|
-
|
77
|
+
it "finds an attachment by a path with a space" do
|
78
|
+
test_string = %(<img src="subfolder/with%20a%20space/test.png" alt="nope" />)
|
79
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<img src="#{@path}files/6/preview" alt="nope">), nil])
|
88
80
|
|
89
|
-
|
90
|
-
|
91
|
-
|
81
|
+
test_string = %(<img src="subfolder/with+a+space/test.png" alt="nope" />)
|
82
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<img src="#{@path}files/6/preview" alt="nope">), nil])
|
83
|
+
end
|
92
84
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
85
|
+
it "finds an attachment even if the link has an extraneous folder" do
|
86
|
+
test_string = %(<img src="anotherfolder/subfolder/test.png" alt="nope" />)
|
87
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<img src="#{@path}files/7/preview" alt="nope">), nil])
|
88
|
+
end
|
97
89
|
|
98
|
-
|
99
|
-
|
100
|
-
|
90
|
+
it "finds an attachment by path if capitalization is different" do
|
91
|
+
expect(@converter.link_resolver).to receive(:attachment_path_id_lookup).twice.and_return({ "subfolder/withCapital/test.png" => "wrong!" })
|
92
|
+
expect(@converter.link_resolver).to receive(:attachment_path_id_lookup).twice.and_return({ "subfolder/withcapital/test.png" => "F" })
|
101
93
|
|
102
|
-
|
103
|
-
|
104
|
-
|
94
|
+
test_string = %(<img src="subfolder/WithCapital/TEST.png" alt="nope" />)
|
95
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<img src="#{@path}files/6/preview" alt="nope">), nil])
|
96
|
+
end
|
105
97
|
|
106
|
-
|
107
|
-
|
98
|
+
it "finds an attachment with query params" do
|
99
|
+
test_string = %(<img src="%24IMS_CC_FILEBASE%24/test.png?canvas_customaction=1&canvas_qs_customparam=1" alt="nope" />)
|
100
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<img src="#{@path}files/5/customaction?customparam=1" alt="nope">), nil])
|
108
101
|
|
109
|
-
|
110
|
-
|
102
|
+
test_string = %(<img src="%24IMS_CC_FILEBASE%24/test.png?canvas_qs_customparam2=3" alt="nope" />)
|
103
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<img src="#{@path}files/5/preview?customparam2=3" alt="nope">), nil])
|
111
104
|
|
112
|
-
|
113
|
-
|
114
|
-
|
105
|
+
test_string = %(<img src="%24IMS_CC_FILEBASE%24/test.png?notarelevantparam" alt="nope" />)
|
106
|
+
expect(@converter.convert_exported_html(test_string)).to eq([%(<img src="#{@path}files/5/preview" alt="nope">), nil])
|
107
|
+
end
|
115
108
|
end
|
116
109
|
|
117
110
|
it "converts picture source srcsets" do
|
@@ -69,10 +69,22 @@ describe CanvasLinkMigrator::LinkResolver do
|
|
69
69
|
expect(link[:new_value]).to eq("/courses/2/discussion_topics/10?foo=bar")
|
70
70
|
end
|
71
71
|
|
72
|
+
it "converts module links" do
|
73
|
+
link = { link_type: :object, type: "modules", migration_id: "J", query: "?foo=bar" }
|
74
|
+
resolver.resolve_link!(link)
|
75
|
+
expect(link[:new_value]).to eq("/courses/2/modules/36?foo=bar")
|
76
|
+
end
|
77
|
+
|
72
78
|
it "converters other links" do
|
73
79
|
link = { link_type: :object, type: "assignments", migration_id: "I", query: "#fie" }
|
74
80
|
resolver.resolve_link!(link)
|
75
81
|
expect(link[:new_value]).to eq("/courses/2/assignments/12#fie")
|
76
82
|
end
|
77
83
|
end
|
84
|
+
|
85
|
+
describe "attachment_path_id_lookup_lower" do
|
86
|
+
it "shows correct lowercase paths" do
|
87
|
+
expect(resolver.attachment_path_id_lookup_lower).to include({ "subfolder/withcapital/test.png" => "migration_id!" })
|
88
|
+
end
|
89
|
+
end
|
78
90
|
end
|
@@ -1,4 +1,10 @@
|
|
1
1
|
{
|
2
|
+
"attachment_path_id_lookup": {
|
3
|
+
"subfolder/test.png": "G",
|
4
|
+
"subfolder/with a space/test.png": "F",
|
5
|
+
"subfolder/withCapital/test.png": "migration_id!",
|
6
|
+
"test.png": "E"
|
7
|
+
},
|
2
8
|
"contains_migration_ids": true,
|
3
9
|
"destination_course": "2",
|
4
10
|
"destination_hosts": [
|
@@ -57,6 +63,16 @@
|
|
57
63
|
"id": "4",
|
58
64
|
"media_entry_id": "m-stuff"
|
59
65
|
}
|
66
|
+
},
|
67
|
+
"G": {
|
68
|
+
"destination": {
|
69
|
+
"id": "7",
|
70
|
+
"media_entry_id": "m-stuff"
|
71
|
+
},
|
72
|
+
"source": {
|
73
|
+
"id": "2",
|
74
|
+
"media_entry_id": "m-stuff"
|
75
|
+
}
|
60
76
|
}
|
61
77
|
},
|
62
78
|
"module_items": {
|
@@ -69,6 +85,16 @@
|
|
69
85
|
}
|
70
86
|
}
|
71
87
|
},
|
88
|
+
"modules": {
|
89
|
+
"J": {
|
90
|
+
"destination": {
|
91
|
+
"id": "36"
|
92
|
+
},
|
93
|
+
"source": {
|
94
|
+
"id": "22"
|
95
|
+
}
|
96
|
+
}
|
97
|
+
},
|
72
98
|
"wiki_pages": {
|
73
99
|
"A": {
|
74
100
|
"destination": {
|
@@ -82,12 +108,6 @@
|
|
82
108
|
}
|
83
109
|
}
|
84
110
|
},
|
85
|
-
"file_tree": {
|
86
|
-
"folder 1": {
|
87
|
-
"id": "1",
|
88
|
-
"sub_folders": {}
|
89
|
-
}
|
90
|
-
},
|
91
111
|
"source_course": "1",
|
92
112
|
"source_host": "pineapple.edu"
|
93
|
-
}
|
113
|
+
}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: canvas_link_migrator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mysti Lilla
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-08-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -39,6 +39,20 @@ dependencies:
|
|
39
39
|
- - ">="
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rack
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
42
56
|
- !ruby/object:Gem::Dependency
|
43
57
|
name: bundler
|
44
58
|
requirement: !ruby/object:Gem::Requirement
|