option for LTI title to not squash assignment name

- adds a `preserveExistingAssignmentName` option that LTI 1.3 tools can
  include in the content item in the Deep Linking response (for
  `assignment_selection` and `submission_type_selection` placements).
  When given, the title (if any) given in the content item will not
  overwrite an existing Assignment name (if already present).
- also remove an old feature flag that has been on for a while

Test plan:
- Have an LTI 1.3 tool with the submission_type_selection placement
  The placement should have the `require_resource_selection` option
  enabled
- check out my latest LTI 1.3 test tool commit with `Extra arbitrary
  content item data (JSON object)` field
- Test the following situations with the submission_type_selection
  placement:
  - assignment name is empty, tool returns a title as usual
    - title should be set
  - assignment name is not empty (different from what tool will set),
    tool returns a title as usual
    - title should be set (overwritten)
  - assignment name is not empty, tool returns a title as usual, tool
    includes
    '"https://canvas.instructure.com/lti/preserveExistingAssignmentName":
    true' in the content item in the deep linking response (use new
    'Extra arbitrary content item data' field in test tool
    - title should not be set
  - assignment name is not empty, tool returns null title (use Extra
    arbitrary contenbt item data) and null lineItem.label (change/remove
    label from line item data field)
- try those with the assignment_selection placement too
- Refresh the page to start afresh and check that when a title is
  supplied but the preserveExistingAssignmentName is given, the title
  still shows up on the card UI, and the assignment can be saved

refs INTEROP-8700
flag=none

Change-Id: I970aaefb58c411fc6b517e440e323ba0380e5136
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/351503
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Steven McGee <steve.mcgee@instructure.com>
QA-Review: Steven McGee <steve.mcgee@instructure.com>
Product-Review: Alexis Nast <alexis.nast@instructure.com>
This commit is contained in:
Evan Battaglia 2024-06-28 18:53:50 -02:30
parent cd58810787
commit 76cd6b7e7d
10 changed files with 66 additions and 84 deletions

View File

@ -392,7 +392,6 @@ class ApplicationController < ActionController::Base
scheduled_page_publication
send_usage_metrics
rce_transform_loaded_content
lti_assignment_page_line_items
mobile_offline_mode
react_discussions_post
instui_nav

View File

@ -120,6 +120,7 @@
<input type="hidden" name="description" id="external_tool_create_description"/>
<input type="hidden" name="available" id="external_tool_create_available"/>
<input type="hidden" name="submission" id="external_tool_create_submission"/>
<input type="hidden" id="external_tool_create_preserve_existing_assignment_name" />
<table>
<tr>
<td colspan="2">

View File

@ -39,8 +39,7 @@ assignment_submission_type_card:
state: hidden
applies_to: SiteAdmin
display_name: Allow submission type resource card to be shown in the assignment edit page
description: |-
When enabled, the new submission type resource card will be shown in the assignment edit page.
description: When enabled, the new submission type resource card will be shown in the assignment edit page.
environments:
development:
state: allowed_on
@ -145,11 +144,6 @@ lti_ags_remove_top_submitted_at:
state: allowed_on
ci:
state: allowed_on
lti_assignment_page_line_items:
state: hidden
display_name: LTI Deep Linking Line Items on Assignment create/edit page
description: If enabled, prefills the assignment form with values from deep link messages on the assignment create/edit page.
applies_to: RootAccount
lti_before_assignment_results:
state: hidden
display_name: Display LTI placement before assignment description after submitting.

View File

@ -143,26 +143,16 @@ as either module items or assignments, based on the presence or absence of the `
property.
#### Assignment Selection Deep Linking
When a tool is launched from the `assignment_selection` placement, properties
from the deep linking response will overwrite existing settings on the
assignment create/edit page. However, the following properties in the deep
linking response behave uniquely:
* `lineItem.label` or `title` (corresponding to the assignment name)
* `lineItem.scoreMaximum` (corresponding to the assignment's "points possible")
* `text` (corresponding to the assignment description body)
Existing values for these corresponding assignment settings will *not* be
overwritten in the following cases:
* The deep linking response omits one of the properties. Leaving out a property
will not erase an existing value.
* The feature flag "LTI Deep Linking Line Items on Assignment create/edit page"
(`lti_assignment_page_line_items`) is disabled. This flag, enabled by
default since August 2023, can be disabled by institution admins. (Note that,
if the corresponding assignment settings are empty, the values from the
deep linking response are used regardless of the feature flag.)
When a tool is launched from the `assignment_selection` placement, any previous
LTI parameters (URL, iframe width, etc.) are overwritten with the information
in the Deep Linking Response. In addition, if the following properties are
present and non-empty in the content item in the Deep Linking Response, they
will set the following fields, potentially overwriting user-inputted values:
- `text` sets/overwrites the assignment description
- `lineItem.scoreMaximum` sets/overwrites the maximum score for the assignment
- `lineItem.label` or `title` sets the assignment name, and overwrites unless
`"https://canvas.instructure.com/lti/preserveExistingAssignmentName": true`
is given in the content item.
### HTML fragment
Full support for required properties.

View File

@ -989,10 +989,6 @@ module Lti
end
context "when on the new assignment page" do
before do
course.root_account.enable_feature! :lti_assignment_page_line_items
end
let(:return_url_params) { super().merge({ placement: "assignment_selection" }) }
let(:content_items) do
[
@ -1002,14 +998,6 @@ module Lti
]
end
context "when assignment edit page feature flag is disabled" do
before do
course.root_account.disable_feature! :lti_assignment_page_line_items
end
it_behaves_like "does nothing"
end
it "does not create a new module" do
expect { subject }.not_to change { course.context_modules.count }
end

View File

@ -640,6 +640,7 @@ EditView.prototype.handleAssignmentSelectionSubmit = function (data) {
height: data['item[iframe][height]'],
},
lineItem: tryJsonParse(data['item[line_item]']),
'https://canvas.instructure.com/lti/preserveExistingAssignmentName': tryJsonParse(data['item[preserveExistingAssignmentName]']),
}
this.handleContentItem(contentItem)
}
@ -651,7 +652,6 @@ EditView.prototype.handleAssignmentSelectionSubmit = function (data) {
* @param {ResourceLinkContentItem} item
*/
EditView.prototype.handleContentItem = function (item) {
const line_items_enabled = !!window.ENV.FEATURES.lti_assignment_page_line_items
this.$externalToolsCustomParams.val(JSON.stringify(item.custom))
this.$externalToolsContentType.val(item.type)
this.$externalToolsContentId.val(item.id || this.selectedTool?.id)
@ -661,33 +661,22 @@ EditView.prototype.handleContentItem = function (item) {
this.$externalToolsIframeWidth.val(item.iframe?.width)
this.$externalToolsIframeHeight.val(item.iframe?.height)
const line_item = item.lineItem
if (line_item) {
this.$externalToolsLineItem.val(JSON.stringify(line_item))
if (
'scoreMaximum' in line_item &&
(line_items_enabled || this.$assignmentPointsPossible.val() === '0')
) {
this.$assignmentPointsPossible.val(line_item.scoreMaximum)
}
const new_assignment_name = 'label' in line_item ? line_item.label : item.title
if (new_assignment_name && (line_items_enabled || this.$name.val() === '')) {
this.$name.val(new_assignment_name)
}
} else {
const new_assignment_name = item.title
if (new_assignment_name && (line_items_enabled || this.$name.val() === '')) {
this.$name.val(new_assignment_name)
const lineItem = item.lineItem
if (lineItem) {
this.$externalToolsLineItem.val(JSON.stringify(lineItem))
if ('scoreMaximum' in lineItem) {
this.$assignmentPointsPossible.val(lineItem.scoreMaximum)
}
}
const description = item.text
if (description) {
const existing_desc = RichContentEditor.callOnRCE(this.$description, 'get_code')
if (line_items_enabled || existing_desc === '') {
RichContentEditor.callOnRCE(this.$description, 'set_code', description)
}
const newAssignmentName = (lineItem && 'label' in lineItem) ? lineItem.label : item.title
const replaceAssignmentName = !item['https://canvas.instructure.com/lti/preserveExistingAssignmentName']
if (newAssignmentName && (replaceAssignmentName || this.$name.val() === '')) {
this.$name.val(newAssignmentName)
}
if (item.text) {
RichContentEditor.callOnRCE(this.$description, 'set_code', item.text)
}
this.renderAssignmentSubmissionTypeContainer()

View File

@ -474,17 +474,36 @@ const lastSubmissionTypeContainerProps = function () {
return calls[calls.length - 1].args[1]
}
test('when a submission_type_selection tool chosen and a resource selected: sets selectedTool and title', function () {
const view = editViewWithSubmissionTypeSelection()
view.$submissionType.val('external_tool_placement_123')
view.$submissionType.trigger('change')
view.handleContentItem({
const makeResourceLinkContentItem = function(overrides={}) {
return {
type: 'ltiResourceLink',
custom: {},
url: 'http://example.com',
title: 'someResource',
lineItem: {},
...overrides,
}
}
test("when a submission_type_selection sends back title and preserveExistingAssignmentName: shows resource and doesn't overwrite title", function () {
const view = editViewWithSubmissionTypeSelection()
view.$submissionType.val('external_tool_placement_123')
view.$submissionType.trigger('change')
view.$('#assignment_name').val('Test Assignment')
const contentItem = makeResourceLinkContentItem({
'https://canvas.instructure.com/lti/preserveExistingAssignmentName': true
})
view.handleContentItem(contentItem)
equal(view.$('#assignment_name').val(), 'Test Assignment')
equal(view.$externalToolsTitle.val(), 'someResource')
})
test('when a submission_type_selection tool chosen and a resource selected: sets selectedTool, title, assignment name', function () {
const view = editViewWithSubmissionTypeSelection()
view.$submissionType.val('external_tool_placement_123')
view.$submissionType.trigger('change')
view.handleContentItem(makeResourceLinkContentItem())
const formData = view.getFormData()
ok(view.selectedTool.require_resource_selection)
@ -492,6 +511,7 @@ test('when a submission_type_selection tool chosen and a resource selected: sets
// Card is shown (props include title):
equal(lastSubmissionTypeContainerProps().resource.title, 'someResource')
equal(view.$('#assignment_name').val(), 'someResource')
})
test('when a submission_type_selection tool chosen, a resource selected, and the resource removed: keeps selectedTool but clears out title', function () {
@ -499,13 +519,7 @@ test('when a submission_type_selection tool chosen, a resource selected, and the
view.$submissionType.val('external_tool_placement_123')
view.$submissionType.trigger('change')
view.handleContentItem({
type: 'ltiResourceLink',
custom: {},
url: 'http://example.com',
title: 'someResourceLinkTitle',
lineItem: {},
})
view.handleContentItem(makeResourceLinkContentItem())
view.handleRemoveResource()
const formData = view.getFormData()
@ -524,13 +538,7 @@ test('when a submission_type_select tool chosen but changed back to generic "Ext
const view = editViewWithSubmissionTypeSelection()
view.$submissionType.val('external_tool_placement_123')
view.$submissionType.trigger('change')
view.handleContentItem({
type: 'ltiResourceLink',
custom: {},
url: 'http://example.com',
title: 'someResourceLinkTitle',
lineItem: {},
})
view.handleContentItem(makeResourceLinkContentItem())
const formData = view.getFormData()
ok(view.selectedTool)
@ -1612,6 +1620,7 @@ QUnit.skip(
'item[available]':
'{"startDateTime": "2023-04-13T00:00:00.000Z", "endDateTime": "2023-04-14T00:00:00.000Z"}',
'item[description]': 'todo_fix_me',
'item[preserveExistingAssignmentName]': 'true',
}
const view = editView()

View File

@ -37,6 +37,7 @@ export type ResourceLinkContentItem = {
startDateTime?: string
endDateTime?: string
}
['https://canvas.instructure.com/lti/preserveExistingAssignmentName']?: boolean
}
const ltiEndpointParams = (lookupUuid?: string | null | undefined) => {

View File

@ -247,6 +247,7 @@ describe('SelectContentDialog: deepLinkingResponseHandler', () => {
<input type='text' id='external_tool_create_iframe_width' />
<input type='text' id='external_tool_create_iframe_height' />
<input type='checkbox' id='external_tool_create_new_tab' />
<input type='text' id='external_tool_create_preserve_existing_assignment_name' />
<div id='context_external_tools_select'>
<span class='domain_message' />
<div class='tools'>
@ -303,6 +304,7 @@ describe('SelectContentDialog: deepLinkingResponseHandler', () => {
endDateTime: '2023-04-21T00:00:00.000Z',
},
text: 'Description text',
'https://canvas.instructure.com/lti/preserveExistingAssignmentName': true,
}
const makeDeepLinkingEvent = (additionalContentItemFields = {}, omitFields = []) => {
const omittedContentItem = Object.fromEntries(
@ -434,6 +436,7 @@ describe('SelectContentDialog: deepLinkingResponseHandler', () => {
'{"startDateTime":"2023-04-12T00:00:00.000Z","endDateTime":"2023-04-21T00:00:00.000Z"}'
)
expect(data['item[description]']).toEqual('Description text')
expect(data['item[preserveExistingAssignmentName]']).toEqual('true')
})
it('recover assignment id from context external tool item data if given', async () => {

View File

@ -282,6 +282,10 @@ export function handleContentItemResult(
setJsonValueIfDefined('#external_tool_create_line_item', result.lineItem)
setJsonValueIfDefined('#external_tool_create_submission', result.submission)
setJsonValueIfDefined('#external_tool_create_available', result.available)
setJsonValueIfDefined(
'#external_tool_create_preserve_existing_assignment_name',
result['https://canvas.instructure.com/lti/preserveExistingAssignmentName']
)
if ('text' in result && typeof result.text === 'string') {
$('#external_tool_create_description').val(result.text)
}
@ -526,6 +530,9 @@ export function extractContextExternalToolItemData() {
'item[description]': $('#external_tool_create_description').val(),
'item[submission]': $('#external_tool_create_submission').val(),
'item[available]': $('#external_tool_create_available').val(),
'item[preserveExistingAssignmentName]': $(
'#external_tool_create_preserve_existing_assignment_name'
).val(),
} as const
}
@ -540,6 +547,7 @@ export function resetExternalToolFields() {
$('#external_tool_create_assignment_id').val('')
$('#external_tool_create_iframe_width').val('')
$('#external_tool_create_iframe_height').val('')
$('#external_tool_create_preserve_existing_assignment_name').val('')
}
export type SelectContentDialogOptions = {