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:
parent
14c0e0b2f7
commit
ed71122c85
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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"]
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
Loading…
Reference in New Issue