send parameters to wiki_index_menu LTI launch

closes ADMIN-2891
flag=none

test plan:
 - Set up new LTI Tool to accept the 4 custom parameters
 - On Pages index, trigger LTI from kabob menu
 - See the 4 new parameters are correctly sent to LTI

Change-Id: Ib3a17d2d9a5d6c5117671061a00365ca86a57d4f
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/213879
Tested-by: Jenkins
Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
Reviewed-by: Marc Phillips <mphillips@instructure.com>
QA-Review: Anju Reddy <areddy@instructure.com>
Product-Review: Carl Kibler <ckibler@instructure.com>
This commit is contained in:
Carl Kibler 2019-10-18 15:01:47 -06:00
parent 14c0e0b2f7
commit ed71122c85
7 changed files with 241 additions and 10 deletions

View File

@ -16,8 +16,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from 'react'
import {string, number, shape, func} from 'prop-types'
import {arrayOf, oneOf, bool, string, shape, func} from 'prop-types'
import CanvasTray from 'jsx/shared/components/CanvasTray'
import $ from 'jquery'
const iframeStyle = {
border: 'none',
@ -27,23 +28,64 @@ const iframeStyle = {
}
const toolShape = shape({
id: number.isRequired,
id: string.isRequired,
title: string.isRequired,
base_url: string.isRequired,
icon_url: string
})
const knownResourceTypes = [
'assignment',
'assignment_group',
'audio',
'discussion_topic',
'document',
'image',
'module',
'quiz',
'page',
'video'
]
ContentTypeExternalToolTray.propTypes = {
tool: toolShape,
placement: string.isRequired,
acceptedResourceTypes: arrayOf(oneOf(knownResourceTypes)).isRequired,
targetResourceType: oneOf(knownResourceTypes).isRequired,
allowItemSelection: bool.isRequired,
selectableItems: arrayOf(oneOf(knownResourceTypes)).isRequired,
onDismiss: func
}
export default function ContentTypeExternalToolTray({tool, onDismiss}) {
const iframeUrl = tool.base_url + '&display=borderless'
export default function ContentTypeExternalToolTray({
tool,
placement,
acceptedResourceTypes,
targetResourceType,
allowItemSelection,
selectableItems,
onDismiss
}) {
const queryParams = {
com_instructure_course_accept_canvas_resource_types: acceptedResourceTypes,
com_instructure_course_canvas_resource_type: targetResourceType,
com_instructure_course_allow_canvas_resource_selection: allowItemSelection,
com_instructure_course_available_canvas_resources: selectableItems,
display: 'borderless',
placement
}
const prefix = tool.base_url.indexOf('?') === -1 ? '?' : '&'
const iframeUrl = `${tool.base_url}${prefix}${$.param(queryParams)}`
return (
<CanvasTray open label={tool.title} onDismiss={onDismiss} placement="end" size="regular">
<iframe style={iframeStyle} src={iframeUrl} title={tool.title} data-lti-launch="true" />
<iframe
data-testid="ltiIframe"
style={iframeStyle}
src={iframeUrl}
title={tool.title}
data-lti-launch="true"
/>
</CanvasTray>
)
}

View File

@ -203,7 +203,15 @@ export default class WikiPageIndexView extends PaginatedCollectionView {
const tool = this.wikiIndexPlacements.find(t => t.id === ev.target.dataset.toolId)
const mountPoint = $('#externalToolMountPoint')[0]
ReactDOM.render(
<ContentTypeExternalToolTray tool={tool} onDismiss={this.closeExternalTool} />,
<ContentTypeExternalToolTray
tool={tool}
placement="wiki_index_menu"
acceptedResourceTypes={['page']}
targetResourceType="page"
allowItemSelection={false}
selectableItems={[]}
onDismiss={this.closeExternalTool}
/>,
mountPoint
)
}

View File

@ -21,17 +21,59 @@ import {render, fireEvent} from '@testing-library/react'
import ContentTypeExternalToolTray from 'compiled/views/wiki/ContentTypeExternalToolTray'
describe('ContentTypeExternalToolTray', () => {
const tool = {id: 1, base_url: 'https://one.lti.com', title: 'First LTI'}
const tool = {id: 1, base_url: 'https://one.lti.com/', title: 'First LTI'}
const onDismiss = jest.fn()
function renderTray(props) {
return render(
<ContentTypeExternalToolTray
tool={tool}
placement="wiki_index_menu"
onDismiss={onDismiss}
acceptedResourceTypes={['page', 'module']}
targetResourceType="page"
allowItemSelection
selectableItems={[{id: '1', name: 'module 1'}]}
{...props}
/>
)
}
it('shows LTI title', () => {
const {getByText} = render(<ContentTypeExternalToolTray tool={tool} onDismiss={onDismiss} />)
const {getByText} = renderTray()
expect(getByText(/first lti/i)).toBeInTheDocument()
})
it('calls onDismiss when close button is clicked', () => {
const {getByText} = render(<ContentTypeExternalToolTray tool={tool} onDismiss={onDismiss} />)
const {getByText} = renderTray()
fireEvent.click(getByText('Close'))
expect(onDismiss.mock.calls.length).toBe(1)
})
describe('constructs iframe src url', () => {
it('adds ? before parameters if none are already present', () => {
expect(tool.base_url).not.toContain('?')
const {getByTestId} = renderTray()
const src = getByTestId('ltiIframe').src
expect(src).toContain(`${tool.base_url}?`)
})
it('appends parameters if some exist already', () => {
tool.base_url = 'https://one.lti.com/?launch_type=wiki_index_menu'
const {getByTestId} = renderTray()
const src = getByTestId('ltiIframe').src
expect(src).toContain(`${tool.base_url}&`)
})
it('includes expected parameters', () => {
const {getByTestId} = renderTray()
const src = getByTestId('ltiIframe').src
expect(src).toContain('com_instructure_course_accept_canvas_resource_types')
expect(src).toContain('com_instructure_course_canvas_resource_type')
expect(src).toContain('com_instructure_course_allow_canvas_resource_selection')
expect(src).toContain('com_instructure_course_available_canvas_resources')
expect(src).toContain('display')
expect(src).toContain('placement')
})
})
})

View File

@ -1159,3 +1159,51 @@ The submission history LTI2 service endpoint.
## com.instructure.Course.accept_canvas_resource_types
Returns the types of resources that can be imported to the current page, forwarded from the request.
Value is an array of one or more values of: ["assignment", "assignment_group", "audio",
"discussion_topic", "document", "image", "module", "quiz", "page", "video"].
**Availability**: *always*
**Launch Parameter**: *com_instructure_course_accept_canvas_resource_types*
```
["page"]
["module"]
["assignment", "discussion_topic", "page", "quiz", "module"]
```
## com.instructure.Course.canvas_resource_type
Returns the target resource type for the current page, forwarded from the request.
Value is the largest logical unit of the page. Possible values are: ["assignment", "assignment_group",
"audio", "discussion_topic", "document", "image", "module", "quiz", "page", "video"]
on Pages Index -> 'page'
on Modules -> 'module'
and so on.
**Availability**: *always*
**Launch Parameter**: *com_instructure_course_canvas_resource_type*
```
page
```
## com.instructure.Course.allow_canvas_resource_selection
Returns whether a content can be imported into a specific group on the page, forwarded from the request.
True for Modules page and Assignment Groups page. False for other content index pages.
**Availability**: *always*
**Launch Parameter**: *com_instructure_course_allow_canvas_resource_selection*
```
true
```
## com.instructure.Course.available_canvas_resources
Returns a list of content groups which can be selected, providing ID and name of each group,
forwarded from the request.
Empty value if com.instructure.Course.allow_canvas_resource_selection is false.
**Availability**: *always*
**Launch Parameter**: *com_instructure_course_available_canvas_resources*
```
[{"id": "3", name: "First Module"}, {"id": "5", name: "Second Module"]
```

View File

@ -1294,6 +1294,58 @@ module Lti
-> {@attachment.usage_rights.legal_copyright},
USAGE_RIGHTS_GUARD
# Returns the types of resources that can be imported to the current page, forwarded from the request.
# Value is an array of one or more values of: ["assignment", "assignment_group", "audio",
# "discussion_topic", "document", "image", "module", "quiz", "page", "video"]
#
# @example
# ```
# ["page"]
# ["module"]
# ["assignment", "discussion_topic", "page", "quiz", "module"]
# ```
register_expansion 'com.instructure.Course.accept_canvas_resource_types', [],
-> { @request.parameters['com_instructure_course_accept_canvas_resource_types'] },
default_name: 'com_instructure_course_accept_canvas_resource_types'
# Returns the target resource type for the current page, forwarded from the request.
# Value is the largest logical unit of the page. Possible values are: ["assignment", "assignment_group",
# "audio", "discussion_topic", "document", "image", "module", "quiz", "page", "video"]
# on Pages Index -> 'page'
# on Modules -> 'module'
# and so on.
#
# @example
# ```
# page
# ```
register_expansion 'com.instructure.Course.canvas_resource_type', [],
-> { @request.parameters['com_instructure_course_canvas_resource_type'] },
default_name: 'com_instructure_course_canvas_resource_type'
# Returns whether a content can be imported into a specific group on the page, forwarded from the request.
# True for Modules page and Assignment Groups page. False for other content index pages.
#
# @example
# ```
# true
# ```
register_expansion 'com.instructure.Course.allow_canvas_resource_selection', [],
-> { @request.parameters['com_instructure_course_allow_canvas_resource_selection'] },
default_name: 'com_instructure_course_allow_canvas_resource_selection'
# Returns a list of content groups which can be selected, providing ID and name of each group,
# forwarded from the request.
# Empty value if com.instructure.Course.allow_canvas_resource_selection is false.
#
# @example
# ```
# [{"id": "3", name: "First Module"}, {"id": "5", name: "Second Module"]
# ```
register_expansion 'com.instructure.Course.available_canvas_resources', [],
-> { @request.parameters['com_instructure_course_available_canvas_resources'] },
default_name: 'com_instructure_course_available_canvas_resources'
private
def sis_pseudonym

View File

@ -108,7 +108,11 @@ module Lti
Canvas.membership.roles
com.instructure.Course.groupIds
com.Instructure.membership.roles
com.instructure.Assignment.anonymous_grading)
com.instructure.Assignment.anonymous_grading
com.instructure.Course.accept_canvas_resource_types
com.instructure.Course.canvas_resource_type
com.instructure.Course.allow_canvas_resource_selection
com.instructure.Course.available_canvas_resources)
}
describe '#supported_capabilities' do

View File

@ -57,6 +57,17 @@ module Lti
allow(request_mock).to receive(:url).and_return('https://localhost')
allow(request_mock).to receive(:host).and_return('/my/url')
allow(request_mock).to receive(:scheme).and_return('https')
allow(request_mock).to receive(:parameters).and_return(
{
'com_instructure_course_accept_canvas_resource_types': ['page', 'module'],
'com_instructure_course_canvas_resource_type': 'page',
'com_instructure_course_allow_canvas_resource_selection': 'true',
'com_instructure_course_available_canvas_resources': [
{'id': '1', 'name': 'item 1'},
{'id': '2', 'name': 'item 2'}
]
}.with_indifferent_access
)
m = double('controller')
allow(m).to receive(:css_url_for).with(:common).and_return('/path/to/common.scss')
allow(m).to receive(:request).and_return(request_mock)
@ -661,6 +672,30 @@ module Lti
expect(exp_hash[:test]).to eq Shard.current.id
end
it 'has substitution for $com.instructure.Course.accept_canvas_resource_types' do
exp_hash = {test: '$com.instructure.Course.accept_canvas_resource_types'}
variable_expander.expand_variables!(exp_hash)
expect(exp_hash[:test]).to eq ["page", "module"]
end
it 'has substitution for $com.instructure.Course.canvas_resource_type' do
exp_hash = {test: '$com.instructure.Course.canvas_resource_type'}
variable_expander.expand_variables!(exp_hash)
expect(exp_hash[:test]).to eq "page"
end
it 'has substitution for $com.instructure.Course.allow_canvas_resource_selection' do
exp_hash = {test: '$com.instructure.Course.allow_canvas_resource_selection'}
variable_expander.expand_variables!(exp_hash)
expect(exp_hash[:test]).to eq 'true'
end
it 'has substitution for $com.instructure.Course.available_canvas_resources' do
exp_hash = {test: '$com.instructure.Course.available_canvas_resources'}
variable_expander.expand_variables!(exp_hash)
expect(exp_hash[:test]).to eq [{"id"=>"1", "name"=>"item 1"}, {"id"=>"2", "name"=>"item 2"}]
end
context 'context is a group' do
let(:variable_expander) { VariableExpander.new(root_account, group, controller, current_user: user, tool: tool) }