Handle content item for default tools

Closes PLAT-4717

Test Plan:
- Configure a default external tool for the account
- Create an assignment in a course of that account
- Verify the tool can return a content item back
  to Canvas
- Verify the UI changes to reflect that content
  was retrieved
- Verify saving the assignment launches to the
  URL sent in the content item
Change-Id: I97fb34346ba49ad58bdafa972f6c3537b665f26d
Reviewed-on: https://gerrit.instructure.com/204525
Tested-by: Jenkins
Reviewed-by: Marc Phillips <mphillips@instructure.com>
QA-Review: Tucker Mcknight <tmcknight@instructure.com>
Product-Review: Weston Dransfield <wdransfield@instructure.com>
This commit is contained in:
wdransfield 2019-08-09 08:25:01 -06:00 committed by Weston Dransfield
parent d766d16326
commit 9d29ecc713
7 changed files with 218 additions and 28 deletions

View File

@ -40,7 +40,7 @@ import deparam from '../../util/deparam'
import SisValidationHelper from '../../util/SisValidationHelper'
import SimilarityDetectionTools from 'jsx/assignments/AssignmentConfigurationTools'
import ModeratedGradingFormFieldGroup from 'jsx/assignments/ModeratedGradingFormFieldGroup'
import DefaultToolForm from 'jsx/assignments/DefaultToolForm'
import DefaultToolForm, { toolSubmissionType } from 'jsx/assignments/DefaultToolForm'
import AssignmentExternalTools from 'jsx/assignments/AssignmentExternalTools'
import * as returnToHelper from '../../../jsx/shared/helpers/returnToHelper'
import 'jqueryui/dialog'
@ -495,6 +495,9 @@ export default class EditView extends ValidatedFormView
@cacheAssignmentSettings()
# Get the submission type if an alias type is used (e.g. default_external_tool)
$(SUBMISSION_TYPE).val(toolSubmissionType($(SUBMISSION_TYPE).val()))
if @dueDateOverrideView.containsSectionsWithoutOverrides()
sections = @dueDateOverrideView.sectionsWithoutOverrides()
missingDateDialog = new MissingDateDialog

View File

@ -19,17 +19,25 @@
import $ from 'jquery'
import axios from 'axios'
import I18n from 'i18n!DefaultToolForm'
import PropTypes from 'prop-types';
import PropTypes from 'prop-types'
import React, {useState, useEffect} from 'react'
import SelectContentDialog from '../../../public/javascripts/select_content_dialog.js'
import usePostMessage from './hooks/usePostMessage'
import {Alert} from '@instructure/ui-alerts'
import {Button} from '@instructure/ui-buttons'
import {View} from '@instructure/ui-layout'
import { Alert } from '@instructure/ui-alerts'
import { Button } from '@instructure/ui-buttons'
import { Text } from '@instructure/ui-elements'
import { View } from '@instructure/ui-layout'
export function toolSubmissionType(submissionType) {
const toolTypes = ['default_external_tool']
return toolTypes.includes(submissionType) ? 'external_tool' : submissionType
}
const DefaultToolForm = props => {
const [launchDefinitions, setLaunchDefinitions] = useState([])
const toolMessageData = usePostMessage('defaultToolContentReady')
const defaultToolData = launchDefinitions.find(definition =>
Object.values(definition.placements).find(placement => placement.url === props.toolUrl)
@ -59,9 +67,18 @@ const DefaultToolForm = props => {
<Button id="default-tool-launch-button" onClick={handleLaunchButton}>
{I18n.t('Add a Question Set')}
</Button>
<Alert variant="info" renderCloseButtonLabel="Close" margin="small small 0 0">
{I18n.t('Click the button above to add a WileyPLUS Question Set')}
</Alert>
{toolMessageData ? (
<Alert variant="success" margin="small small 0 0">
<Text weight="bold">{toolMessageData.content.title}</Text><br/>
<Text>{I18n.t('Successfully Added')}</Text>
</Alert>
) : (
<Alert variant="info" margin="small small 0 0">
{I18n.t('Click the button above to add a WileyPLUS Question Set')}
</Alert>
)}
{defaultToolData && (
<div style={{display: 'none'}}>
<ul className="tools">

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 - present Instructure, Inc.
* Copyright (C) 2019 - present Instructure, Inc.
*
* This file is part of Canvas.
*
@ -18,7 +18,7 @@
import React from 'react'
import {mount} from 'enzyme'
import DefaultToolForm from '../DefaultToolForm'
import DefaultToolForm, { toolSubmissionType } from '../DefaultToolForm'
import SelectContentDialog from '../../../../public/javascripts/select_content_dialog.js'
const newProps = (overrides = {}) => ({
@ -29,26 +29,38 @@ const newProps = (overrides = {}) => ({
...overrides
})
let wrapper = 'empty wrapper'
describe('DefaultToolForm', () => {
let wrapper = 'empty wrapper'
afterEach(() => {
wrapper.unmount()
afterEach(() => {
wrapper.unmount()
})
it('renders a button to launch the tool', () => {
wrapper = mount(<DefaultToolForm {...newProps()} />)
expect(wrapper.find('#default-tool-launch-button')).toBeTruthy()
})
it('launches the tool when the button is clicked', () => {
SelectContentDialog.Events.onContextExternalToolSelect = jest.fn()
wrapper = mount(<DefaultToolForm {...newProps()} />)
wrapper.find('#default-tool-launch-button').first().simulate('click')
expect(SelectContentDialog.Events.onContextExternalToolSelect).toHaveBeenCalled()
SelectContentDialog.Events.onContextExternalToolSelect.mockRestore()
})
it('renders the information mesage', () => {
wrapper = mount(<DefaultToolForm {...newProps()} />)
expect(wrapper.find('Alert').html()).toContain('Click the button above to add a WileyPLUS Question Set')
})
})
it('renders a button to launch the tool', () => {
wrapper = mount(<DefaultToolForm {...newProps()} />)
expect(wrapper.find('#default-tool-launch-button')).toBeTruthy()
})
describe('toolSubmissionType', () => {
it('returns "external_tool" if the submission type is "default_external_tool"', () => {
expect(toolSubmissionType('default_external_tool')).toEqual('external_tool')
})
it('launches the tool when the button is clicked', () => {
SelectContentDialog.Events.onContextExternalToolSelect = jest.fn()
wrapper = mount(<DefaultToolForm {...newProps()} />)
wrapper.find('#default-tool-launch-button').first().simulate('click')
expect(SelectContentDialog.Events.onContextExternalToolSelect).toHaveBeenCalled()
SelectContentDialog.Events.onContextExternalToolSelect.mockRestore()
})
it('renders the information mesage', () => {
wrapper = mount(<DefaultToolForm {...newProps()} />)
expect(wrapper.find('Alert').html()).toContain('Click the button above to add a WileyPLUS Question Set')
it('returns the submission type if it is not a tool submission type', () => {
expect(toolSubmissionType('online')).toEqual('online')
})
})

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2019 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, {useState, useEffect} from 'react'
export default function usePostMessage(expectedMessageType) {
const [messageData, setMessageData] = useState(null)
useEffect(() => {
function handlePostMessage(postMessage) {
const {messageType} = postMessage.data
if (
postMessage.origin === ENV.DEEP_LINKING_POST_MESSAGE_ORIGIN &&
messageType === expectedMessageType
) {
setMessageData(postMessage.data)
}
}
window.addEventListener('message', handlePostMessage, false)
return () => {
window.removeEventListener('message', handlePostMessage)
}
})
return messageData
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2019 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import setDefaultToolValues from '../setDefaultToolValues'
describe('setDefaultToolValues', () => {
const definition_type = "ContextExternalTool"
const definition_id = '22'
const tool = {
definition_type,
definition_id
}
const url = 'https://www.test-tool.com/lti_launch'
const result = {
url
}
const postMessageOrigin = window.ENV.DEEP_LINKING_POST_MESSAGE_ORIGIN
beforeAll(() => {
window.ENV.DEEP_LINKING_POST_MESSAGE_ORIGIN = 'canvas.instructure.com'
})
beforeEach(() => {
document.body.innerHTML =
'<input id="assignment_external_tool_tag_attributes_content_type" type="hidden"/>' +
'<input id="assignment_external_tool_tag_attributes_content_id" type="hidden"/>' +
'<input id="assignment_external_tool_tag_attributes_url" type="hidden"/>'
window.postMessage = jest.fn()
setDefaultToolValues(result, tool)
})
afterEach(() => { window.postMessage.mockReset() })
afterAll(() => {
window.ENV.DEEP_LINKING_POST_MESSAGE_ORIGIN = postMessageOrigin
window.postMessage.mockRestore()
})
it('sends a postMessage to the window with results', () => {
expect(window.postMessage).toHaveBeenCalledWith({
messageType: 'defaultToolContentReady',
content: result
}, 'canvas.instructure.com')
})
it('sets the definition type', () => {
expect(document.querySelector('#assignment_external_tool_tag_attributes_content_type').value)
.toEqual(definition_type)
})
it('sets the definition id', () => {
expect(document.querySelector('#assignment_external_tool_tag_attributes_content_id').value)
.toEqual(definition_id)
})
it('sets the tool URL', () => {
expect(document.querySelector('#assignment_external_tool_tag_attributes_url').value)
.toEqual(url)
})
})

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2019 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import $ from 'jquery'
export default function setDefaultToolValues(result, tool) {
$('#assignment_external_tool_tag_attributes_content_type').val(tool.definition_type)
$('#assignment_external_tool_tag_attributes_content_id').val(tool.definition_id)
$('#assignment_external_tool_tag_attributes_url').val(result.url)
window.postMessage({
messageType: 'defaultToolContentReady',
content: result
}, ENV.DEEP_LINKING_POST_MESSAGE_ORIGIN)
}

View File

@ -27,6 +27,7 @@ import htmlEscape from 'str/htmlEscape'
import { uploadFile } from 'jsx/shared/upload_file'
import iframeAllowances from 'jsx/external_apps/lib/iframeAllowances'
import SelectContent from './lti/select_content'
import setDefaultToolValues from './lti/setDefaultToolValues'
import processSingleContentItem from 'jsx/deep_linking/processors/processSingleContentItem'
import {findLinkForService, getUserServices} from './findLinkForService'
import './jquery.instructure_date_and_time' /* datetime_field */
@ -94,6 +95,9 @@ import './jquery.templateData'
}
SelectContentDialog.handleContentItemResult = function(result, tool) {
if (ENV.DEFAULT_ASSIGNMENT_TOOL_NAME && ENV.DEFAULT_ASSIGNMENT_TOOL_URL) {
setDefaultToolValues(result, tool)
}
$("#external_tool_create_url").val(result.url)
$("#external_tool_create_title").val(result.title || tool.name)
$("#context_external_tools_select .domain_message").hide()