Skip to content

Commit

Permalink
Add an optional title option for assignment configuration
Browse files Browse the repository at this point in the history
Forward the value in deep linking responses.
  • Loading branch information
marcospri committed Jul 12, 2023
1 parent b5cb0eb commit 37fdc06
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 62 deletions.
72 changes: 41 additions & 31 deletions lms/views/lti/deep_linking.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class DeepLinkingFieldsRequestSchema(JSONPyramidRequestSchema):
deep_linking_settings = fields.Dict(required=False, allow_none=True)
content_item_return_url = fields.Str(required=True)
content = fields.Dict(required=True)
title = fields.Str(required=False, allow_none=True)

extra_params = fields.Dict(required=False)

Expand All @@ -111,6 +112,20 @@ def file_picker_to_form_fields_v13(self):

assignment_configuration = self._get_assignment_configuration(self.request)

content_item = {
"type": "ltiResourceLink",
# The URL we will be called back on when the
# assignment is launched from the LMS
"url": self.misc_plugin.get_deeplinking_launch_url(
self.request, assignment_configuration
),
# These values should be passed back to us as custom
# LTI params, but Canvas doesn't seem to.
"custom": assignment_configuration,
}
if title := assignment_configuration.get("title"):
content_item["title"] = title

now = datetime.utcnow()
message = {
"exp": now + timedelta(hours=1),
Expand All @@ -123,17 +138,7 @@ def file_picker_to_form_fields_v13(self):
"https://purl.imsglobal.org/spec/lti/claim/message_type": "LtiDeepLinkingResponse",
"https://purl.imsglobal.org/spec/lti/claim/version": "1.3.0",
"https://purl.imsglobal.org/spec/lti-dl/claim/content_items": [
{
"type": "ltiResourceLink",
# The URL we will be called back on when the
# assignment is launched from the LMS
"url": self.misc_plugin.get_deeplinking_launch_url(
self.request, assignment_configuration
),
# These values should be passed back to us as custom
# LTI params, but Canvas doesn't seem to.
"custom": assignment_configuration,
}
content_item
],
}

Expand Down Expand Up @@ -181,28 +186,30 @@ def file_picker_to_form_fields_v11(self):
data=assignment_configuration,
)
)
return {
"content_items": json.dumps(
{
"@context": "http://purl.imsglobal.org/ctx/lti/v1/ContentItem",
"@graph": [
{
"@type": "LtiLinkItem",
"mediaType": "application/vnd.ims.lti.v1.ltilink",
# The URL we will be called back on when the
# assignment is launched from the LMS
"url": self.misc_plugin.get_deeplinking_launch_url(
self.request, assignment_configuration
),
# These values should be passed back to us as custom
# LTI params, but Canvas doesn't seem to.
"custom": assignment_configuration,
},
],
}
)

content_item = {
"@type": "LtiLinkItem",
"mediaType": "application/vnd.ims.lti.v1.ltilink",
# The URL we will be called back on when the
# assignment is launched from the LMS
"url": self.misc_plugin.get_deeplinking_launch_url(
self.request, assignment_configuration
),
# These values should be passed back to us as custom
# LTI params, but Canvas doesn't seem to.
"custom": assignment_configuration,
}

if title := assignment_configuration.get("title"):
content_item["title"] = title

message = {
"@context": "http://purl.imsglobal.org/ctx/lti/v1/ContentItem",
"@graph": [content_item],
}

return {"content_items": json.dumps(message)}

@staticmethod
def _get_assignment_configuration(request) -> dict:
"""Turn front-end content information into assignment configuration."""
Expand All @@ -216,6 +223,9 @@ def _get_assignment_configuration(request) -> dict:
if value is not None
}

if title := request.parsed_params.get("title"):
params["title"] = title

if content["type"] == "file":
params["canvas_file"] = "true"
params["file_id"] = content["file"]["id"]
Expand Down
86 changes: 55 additions & 31 deletions tests/unit/lms/views/lti/deep_linking_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def context(self):
@pytest.mark.usefixtures("application_instance_service", "misc_plugin")
class TestDeepLinkingFieldsView:
@freeze_time("2022-04-04")
@pytest.mark.parametrize("title", [None, "title"])
def test_it_for_v13(
self,
jwt_service,
Expand All @@ -70,30 +71,38 @@ def test_it_for_v13(
pyramid_request,
misc_plugin,
_get_assignment_configuration,
title,
):
if title:
_get_assignment_configuration.return_value = {"title": title}

fields = views.file_picker_to_form_fields_v13()

jwt_service.encode_with_private_key.assert_called_once_with(
{
"exp": datetime(2022, 4, 4, 1, 0),
"iat": datetime(2022, 4, 4, 0, 0),
"iss": application_instance.lti_registration.client_id,
"sub": application_instance.lti_registration.client_id,
"aud": application_instance.lti_registration.issuer,
"nonce": uuid.uuid4().hex,
"https://purl.imsglobal.org/spec/lti/claim/deployment_id": application_instance.deployment_id,
"https://purl.imsglobal.org/spec/lti/claim/message_type": "LtiDeepLinkingResponse",
"https://purl.imsglobal.org/spec/lti/claim/version": "1.3.0",
"https://purl.imsglobal.org/spec/lti-dl/claim/content_items": [
{
"type": "ltiResourceLink",
"url": misc_plugin.get_deeplinking_launch_url.return_value,
"custom": _get_assignment_configuration.return_value,
}
],
"https://purl.imsglobal.org/spec/lti-dl/claim/data": sentinel.deep_linking_settings_data,
}
)
message = {
"exp": datetime(2022, 4, 4, 1, 0),
"iat": datetime(2022, 4, 4, 0, 0),
"iss": application_instance.lti_registration.client_id,
"sub": application_instance.lti_registration.client_id,
"aud": application_instance.lti_registration.issuer,
"nonce": uuid.uuid4().hex,
"https://purl.imsglobal.org/spec/lti/claim/deployment_id": application_instance.deployment_id,
"https://purl.imsglobal.org/spec/lti/claim/message_type": "LtiDeepLinkingResponse",
"https://purl.imsglobal.org/spec/lti/claim/version": "1.3.0",
"https://purl.imsglobal.org/spec/lti-dl/claim/content_items": [
{
"type": "ltiResourceLink",
"url": misc_plugin.get_deeplinking_launch_url.return_value,
"custom": _get_assignment_configuration.return_value,
}
],
"https://purl.imsglobal.org/spec/lti-dl/claim/data": sentinel.deep_linking_settings_data,
}
if title:
message["https://purl.imsglobal.org/spec/lti-dl/claim/content_items"][0][
"title"
] = title

jwt_service.encode_with_private_key.assert_called_once_with(message)
LTIEvent.assert_called_once_with(
request=pyramid_request,
type=LTIEvent.Type.DEEP_LINKING,
Expand All @@ -119,15 +128,19 @@ def test_it_for_v13_missing_deep_linking_settings_data(
not in message.last_matched() # pylint: disable=unsupported-membership-test
)

@pytest.mark.parametrize("title", [None, "title"])
def test_it_for_v11(
self,
views,
_get_assignment_configuration,
pyramid_request,
LTIEvent,
misc_plugin,
title,
):
misc_plugin.get_deeplinking_launch_url.return_value = "LAUNCH_URL"
if title:
_get_assignment_configuration.return_value = {"title": title}

fields = views.file_picker_to_form_fields_v11()

Expand All @@ -138,7 +151,7 @@ def test_it_for_v11(
)
pyramid_request.registry.notify.has_call_with(LTIEvent.return_value)

assert json.loads(fields["content_items"]) == {
content_items = {
"@context": "http://purl.imsglobal.org/ctx/lti/v1/ContentItem",
"@graph": [
{
Expand All @@ -150,8 +163,13 @@ def test_it_for_v11(
],
}

if title:
content_items["@graph"][0]["title"] = title

assert json.loads(fields["content_items"]) == content_items

@pytest.mark.parametrize(
"content,output_params",
"content,expected_from_content",
[
(
{"type": "file", "file": {"id": 1}},
Expand All @@ -164,24 +182,30 @@ def test_it_for_v11(
],
)
@pytest.mark.parametrize(
"extra_params,extra_expected",
"data,expected",
[
({}, {}),
({"extra": "value", "none_value": None}, {"extra": "value"}),
({"title": "title"}, {"title": "title"}),
(
{"extra_params": {"extra": "value", "none_value": None}},
{"extra": "value"},
),
],
)
def test_get_assignment_configuration(
self, content, output_params, extra_params, extra_expected, pyramid_request
self,
content,
expected_from_content,
pyramid_request,
data,
expected,
):
pyramid_request.parsed_params.update(
{"content": content, "extra_params": extra_params}
)
pyramid_request.parsed_params.update({"content": content, **data})

# pylint:disable=protected-access
config = DeepLinkingFieldsViews._get_assignment_configuration(pyramid_request)

output_params.update(extra_expected)
assert config == output_params
assert config == {**expected, **expected_from_content}

def test_it_with_unknown_file_type(self, pyramid_request):
pyramid_request.parsed_params.update({"content": {"type": "other"}})
Expand Down

0 comments on commit 37fdc06

Please sign in to comment.