Create default tool assignment from "quick create"
Closes PLAT-4695 Test Plan: - Create a a default tool assignment from the quick create modal in the assignment index page - Edit the new assignment - Verify you can select content from the default tool as the default - Verify you can save and launch the assignment - Verify you can create non-default external tool assignments as before - Verify you can create non-default and default external tool assignments withouth the "quick create" modal Change-Id: I94578f99c3d200e3dac57a13a7d5f7157413618b Reviewed-on: https://gerrit.instructure.com/204847 Tested-by: Jenkins Reviewed-by: Clint Furse <cfurse@instructure.com> Reviewed-by: Landon Gilbert-Bland <lbland@instructure.com> QA-Review: Tucker Mcknight <tmcknight@instructure.com> Product-Review: Jesse Poulos <jpoulos@instructure.com>
This commit is contained in:
parent
d0146696db
commit
a63c1f0468
|
@ -29,6 +29,7 @@ import GradingPeriodsHelper from 'jsx/grading/helpers/GradingPeriodsHelper'
|
|||
import tz from 'timezone'
|
||||
import numberHelper from 'jsx/shared/helpers/numberHelper'
|
||||
import PandaPubPoller from '../util/PandaPubPoller'
|
||||
import { matchingToolUrls } from './LtiAssignmentHelpers'
|
||||
|
||||
isAdmin = () ->
|
||||
_.includes(ENV.current_user_roles, 'admin')
|
||||
|
@ -63,7 +64,9 @@ export default class Assignment extends Model
|
|||
isDiscussionTopic: => @_hasOnlyType 'discussion_topic'
|
||||
isPage: => @_hasOnlyType 'wiki_page'
|
||||
isExternalTool: => @_hasOnlyType 'external_tool'
|
||||
|
||||
defaultToolName: => ENV.DEFAULT_ASSIGNMENT_TOOL_NAME
|
||||
defaultToolUrl: => ENV.DEFAULT_ASSIGNMENT_TOOL_URL
|
||||
isNotGraded: => @_hasOnlyType 'not_graded'
|
||||
isAssignment: =>
|
||||
! _.includes @_submissionTypes(), 'online_quiz', 'discussion_topic',
|
||||
|
@ -152,11 +155,29 @@ export default class Assignment extends Model
|
|||
return @_submissionTypes() unless arguments.length > 0
|
||||
@set 'submission_types', submissionTypes
|
||||
|
||||
isDefaultTool: =>
|
||||
@submissionType() == 'external_tool' && (@defaultToolSelected() || @isQuickCreateDefaultTool())
|
||||
|
||||
isQuickCreateDefaultTool: =>
|
||||
@submissionTypes().includes('default_external_tool')
|
||||
|
||||
defaultToolSelected: =>
|
||||
matchingToolUrls(
|
||||
@defaultToolUrl(),
|
||||
@externalToolUrl()
|
||||
)
|
||||
|
||||
isNonDefaultExternalTool: =>
|
||||
# The assignment is type 'external_tool' and the default tool is not selected
|
||||
# or chosen from the "quick create" assignment index modal.
|
||||
@submissionType() == 'external_tool' && !@isDefaultTool()
|
||||
|
||||
submissionType: =>
|
||||
submissionTypes = @_submissionTypes()
|
||||
if _.includes(submissionTypes, 'none') || submissionTypes.length == 0 then 'none'
|
||||
else if _.includes submissionTypes, 'on_paper' then 'on_paper'
|
||||
else if _.includes submissionTypes, 'external_tool' then 'external_tool'
|
||||
else if _.includes submissionTypes, 'default_external_tool' then 'external_tool'
|
||||
else 'online'
|
||||
|
||||
expectsSubmission: =>
|
||||
|
@ -458,7 +479,7 @@ export default class Assignment extends Model
|
|||
'secureParams', 'inClosedGradingPeriod', 'dueDateRequired',
|
||||
'submissionTypesFrozen', 'anonymousInstructorAnnotations',
|
||||
'anonymousGrading', 'gradersAnonymousToGraders', 'showGradersAnonymousToGradersCheckbox',
|
||||
'defaultToolName'
|
||||
'defaultToolName', 'isDefaultTool', 'isNonDefaultExternalTool'
|
||||
]
|
||||
|
||||
hash =
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
export function matchingToolUrls(firstUrl, secondUrl) {
|
||||
if (!(firstUrl && secondUrl)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const firstUrlParsed = new URL(firstUrl)
|
||||
const secondUrlParsed = new URL(secondUrl)
|
||||
return firstUrlParsed.origin === secondUrlParsed.origin
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 { matchingToolUrls } from '../LtiAssignmentHelpers'
|
||||
|
||||
describe('#matchingToolUrls', () => {
|
||||
let firstUrl
|
||||
let secondUrl
|
||||
|
||||
const subject = () => matchingToolUrls(firstUrl, secondUrl)
|
||||
|
||||
describe('when domains and protocol match', () => {
|
||||
beforeEach(() => {
|
||||
firstUrl = 'https://www.mytool.com/blti'
|
||||
secondUrl = 'https://www.mytool.com/blti/resourceid'
|
||||
})
|
||||
|
||||
it('returns true', () => {
|
||||
expect(subject()).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when domains match but protocols do not', () => {
|
||||
beforeEach(() => {
|
||||
firstUrl = 'http://www.mytool.com/blti'
|
||||
secondUrl = 'https://www.mytool.com/blti/resourceid'
|
||||
})
|
||||
|
||||
it('returns false', () => {
|
||||
expect(subject()).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when domains do not match but protocols do', () => {
|
||||
beforeEach(() => {
|
||||
firstUrl = 'https://www.mytool.com/blti'
|
||||
secondUrl = 'https://www.other-tool.com/blti'
|
||||
})
|
||||
|
||||
it('returns false', () => {
|
||||
expect(subject()).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when neither domain nor protocols match', () => {
|
||||
beforeEach(() => {
|
||||
firstUrl = 'https://www.mytool.com/blti'
|
||||
secondUrl = 'http://www.other-tool.com/blti'
|
||||
})
|
||||
|
||||
it('returns false', () => {
|
||||
expect(subject()).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the first url is falsey', () => {
|
||||
beforeEach(() => {
|
||||
firstUrl = undefined
|
||||
secondUrl = 'https://www.other-tool.com/blti'
|
||||
})
|
||||
|
||||
it('returns false', () => {
|
||||
expect(subject()).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the last url is falsey', () => {
|
||||
beforeEach(() => {
|
||||
firstUrl = 'https://www.mytool.com/blti'
|
||||
secondUrl = undefined
|
||||
})
|
||||
|
||||
it('returns false', () => {
|
||||
expect(subject()).toEqual(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -66,6 +66,8 @@ export default class CreateAssignmentView extends DialogFormView
|
|||
|
||||
getFormData: =>
|
||||
data = super
|
||||
submission_type_select = document.querySelector('select[name="submission_types"]')
|
||||
|
||||
unfudged = $.unfudgeDateForProfileTimezone(data.due_at)
|
||||
data.due_at = @_getDueAt(unfudged) if unfudged?
|
||||
data.published = true if @shouldPublish
|
||||
|
@ -124,7 +126,8 @@ export default class CreateAssignmentView extends DialogFormView
|
|||
uniqLabel: uniqLabel
|
||||
disableDueAt: @disableDueAt()
|
||||
postToSISName: ENV.SIS_NAME
|
||||
isInClosedPeriod: @model.inClosedGradingPeriod()
|
||||
isInClosedPeriod: @model.inClosedGradingPeriod(),
|
||||
defaultToolName: ENV.DEFAULT_ASSIGNMENT_TOOL_NAME
|
||||
|
||||
# master_course_restrictions only apply if this is a child course
|
||||
# and is restricted by a master course.
|
||||
|
|
|
@ -306,11 +306,16 @@ export default class EditView extends ValidatedFormView
|
|||
defaultExternalToolUrl: =>
|
||||
ENV.DEFAULT_ASSIGNMENT_TOOL_URL
|
||||
|
||||
defaultExternalToolName: =>
|
||||
ENV.DEFAULT_ASSIGNMENT_TOOL_NAME
|
||||
|
||||
renderDefaultExternalTool: =>
|
||||
props = {
|
||||
toolDialog: $("#resource_selection_dialog"),
|
||||
courseId: ENV.COURSE_ID,
|
||||
toolUrl: @defaultExternalToolUrl()
|
||||
toolUrl: @defaultExternalToolUrl(),
|
||||
toolName: @defaultExternalToolName(),
|
||||
previouslySelected: @assignment.defaultToolSelected()
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
|
|
|
@ -69,6 +69,9 @@ class AssignmentsController < ApplicationController
|
|||
DUE_DATE_REQUIRED_FOR_ACCOUNT: due_date_required_for_account,
|
||||
DIRECT_SHARE_ENABLED: @domain_root_account&.feature_enabled?(:direct_share),
|
||||
}
|
||||
|
||||
set_default_tool_env!(@context, hash)
|
||||
|
||||
js_env(hash)
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -521,8 +524,6 @@ class AssignmentsController < ApplicationController
|
|||
rce_js_env
|
||||
@assignment ||= @context.assignments.active.find(params[:id])
|
||||
if authorized_action(@assignment, @current_user, @assignment.new_record? ? :create : :update)
|
||||
root_account_settings = @context.root_account.settings
|
||||
|
||||
@assignment.title = params[:title] if params[:title]
|
||||
@assignment.due_at = params[:due_at] if params[:due_at]
|
||||
@assignment.points_possible = params[:points_possible] if params[:points_possible]
|
||||
|
@ -615,10 +616,7 @@ class AssignmentsController < ApplicationController
|
|||
hash[:active_grading_periods] = GradingPeriod.json_for(@context, @current_user)
|
||||
end
|
||||
|
||||
if root_account_settings[:default_assignment_tool_url] && root_account_settings[:default_assignment_tool_name]
|
||||
hash[:DEFAULT_ASSIGNMENT_TOOL_URL] = root_account_settings[:default_assignment_tool_url]
|
||||
hash[:DEFAULT_ASSIGNMENT_TOOL_NAME] = root_account_settings[:default_assignment_tool_name]
|
||||
end
|
||||
set_default_tool_env!(@context, hash)
|
||||
|
||||
hash[:ANONYMOUS_GRADING_ENABLED] = @context.feature_enabled?(:anonymous_marking)
|
||||
hash[:MODERATED_GRADING_ENABLED] = @context.feature_enabled?(:moderated_grading)
|
||||
|
@ -663,6 +661,14 @@ class AssignmentsController < ApplicationController
|
|||
|
||||
protected
|
||||
|
||||
def set_default_tool_env!(context, hash)
|
||||
root_account_settings = context.root_account.settings
|
||||
if root_account_settings[:default_assignment_tool_url] && root_account_settings[:default_assignment_tool_name]
|
||||
hash[:DEFAULT_ASSIGNMENT_TOOL_URL] = root_account_settings[:default_assignment_tool_url]
|
||||
hash[:DEFAULT_ASSIGNMENT_TOOL_NAME] = root_account_settings[:default_assignment_tool_name]
|
||||
end
|
||||
end
|
||||
|
||||
def show_moderate_env
|
||||
can_view_grader_identities = @assignment.can_view_other_grader_identities?(@current_user)
|
||||
|
||||
|
|
|
@ -46,6 +46,12 @@ const DefaultToolForm = props => {
|
|||
const launchDefinitionUrl = () =>
|
||||
`/api/v1/courses/${props.courseId}/lti_apps/launch_definitions?per_page=100&placements%5B%5D=assignment_selection&placements%5B%5D=resource_selection`
|
||||
|
||||
const contentTitle = () => {
|
||||
if (toolMessageData) {
|
||||
return toolMessageData.content && toolMessageData.content.title
|
||||
}
|
||||
return props.toolName
|
||||
}
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const result = await axios.get(launchDefinitionUrl())
|
||||
|
@ -68,9 +74,9 @@ const DefaultToolForm = props => {
|
|||
{I18n.t('Add a Question Set')}
|
||||
</Button>
|
||||
|
||||
{toolMessageData ? (
|
||||
{toolMessageData || props.previouslySelected ? (
|
||||
<Alert variant="success" margin="small small 0 0">
|
||||
<Text weight="bold">{toolMessageData.content.title}</Text><br/>
|
||||
<Text weight="bold">{contentTitle()}</Text><br/>
|
||||
<Text>{I18n.t('Successfully Added')}</Text>
|
||||
</Alert>
|
||||
) : (
|
||||
|
@ -97,7 +103,9 @@ const DefaultToolForm = props => {
|
|||
|
||||
DefaultToolForm.propTypes = {
|
||||
toolUrl: PropTypes.string.isRequired,
|
||||
courseId: PropTypes.number.isRequired
|
||||
courseId: PropTypes.number.isRequired,
|
||||
toolName: PropTypes.string.isRequired,
|
||||
previouslySelected: PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
export default DefaultToolForm
|
||||
|
|
|
@ -24,7 +24,8 @@ import SelectContentDialog from '../../../../public/javascripts/select_content_d
|
|||
const newProps = (overrides = {}) => ({
|
||||
...{
|
||||
toolUrl: 'https://www.default-tool.com/blti',
|
||||
courseId: 1
|
||||
courseId: 1,
|
||||
toolName: 'Awesome Tool'
|
||||
},
|
||||
...overrides
|
||||
})
|
||||
|
@ -53,6 +54,11 @@ describe('DefaultToolForm', () => {
|
|||
wrapper = mount(<DefaultToolForm {...newProps()} />)
|
||||
expect(wrapper.find('Alert').html()).toContain('Click the button above to add a WileyPLUS Question Set')
|
||||
})
|
||||
|
||||
it('renders the success message if previouslySelected is true', () => {
|
||||
wrapper = mount(<DefaultToolForm {...newProps({previouslySelected: true})} />)
|
||||
expect(wrapper.find('Alert').html()).toContain('Successfully Added')
|
||||
})
|
||||
})
|
||||
|
||||
describe('toolSubmissionType', () => {
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
</label>
|
||||
<div class="controls">
|
||||
<select type="text" id="{{uniqLabel}}_assignment_type" name="submission_types">
|
||||
{{#if defaultToolName}}
|
||||
<option value="default_external_tool" data-default-tool="true">{{defaultToolName}}</option>
|
||||
{{/if}}
|
||||
<option value="none">{{#t "assignment"}}Assignment{{/t}}</option>
|
||||
<option value="discussion_topic">{{#t "discussion_type"}}Discussion{{/t}}</option>
|
||||
<option value="online_quiz">{{#t "quiz_type"}}Quiz{{/t}}</option>
|
||||
|
|
|
@ -6,14 +6,13 @@
|
|||
</div>
|
||||
<div class="form-column-right">
|
||||
<div class="border border-trbl border-round">
|
||||
|
||||
{{!-- Submission type accepted --}}
|
||||
<select id="assignment_submission_type"
|
||||
name="submission_type"
|
||||
aria-controls="assignment_online_submission_types,assignment_external_tool_settings,assignment_group_selector,assignment_peer_reviews_fields"
|
||||
{{disabledIfIncludes frozenAttributes "submission_types"}}>
|
||||
{{#if defaultToolName}}
|
||||
<option value="default_external_tool" {{selectedIf submissionType "default_external_tool"}}>
|
||||
<option value="default_external_tool" {{selectedIf isDefaultTool}}>
|
||||
{{defaultToolName}}
|
||||
</option>
|
||||
{{/if}}
|
||||
|
@ -26,7 +25,7 @@
|
|||
<option value="on_paper" {{selectedIf submissionType "on_paper"}}>
|
||||
{{#t "submission_types.on_paper"}}On Paper{{/t}}
|
||||
</option>
|
||||
<option value="external_tool" {{selectedIf submissionType "external_tool"}}>
|
||||
<option value="external_tool" {{selectedIf isNonDefaultExternalTool}}>
|
||||
{{#t "submission_types.external_tool"}}External Tool{{/t}}
|
||||
</option>
|
||||
</select>
|
||||
|
|
|
@ -545,7 +545,7 @@ module Api::V1::Assignment
|
|||
if assignment_params['submission_types'].present? &&
|
||||
!assignment_params['submission_types'].all? do |s|
|
||||
return false if s == 'wiki_page' && !self.context.try(:feature_enabled?, :conditional_release)
|
||||
API_ALLOWED_SUBMISSION_TYPES.include?(s)
|
||||
API_ALLOWED_SUBMISSION_TYPES.include?(s) || (s == 'default_external_tool' && assignment.unpublished?)
|
||||
end
|
||||
assignment.errors.add('assignment[submission_types]',
|
||||
I18n.t('assignments_api.invalid_submission_types',
|
||||
|
|
|
@ -95,6 +95,65 @@ test('returns false if record is discussion topic', () => {
|
|||
equal(assignment.isDiscussionTopic(), false)
|
||||
})
|
||||
|
||||
QUnit.module('Assignment#isDefaultTool', {
|
||||
setup() {
|
||||
fakeENV.setup({
|
||||
DEFAULT_ASSIGNMENT_TOOL_NAME: 'Default Tool',
|
||||
DEFAULT_ASSIGNMENT_TOOL_URL: 'https://www.test.com/blti'
|
||||
})
|
||||
},
|
||||
teardown() {
|
||||
fakeENV.teardown()
|
||||
}
|
||||
})
|
||||
|
||||
test('returns true if submissionType is "external_tool" and default tool is selected', () => {
|
||||
const assignment = new Assignment({
|
||||
name: 'foo',
|
||||
external_tool_tag_attributes: {
|
||||
url: 'https://www.test.com/blti?foo'
|
||||
}
|
||||
})
|
||||
assignment.submissionTypes(['external_tool'])
|
||||
equal(assignment.isDefaultTool(), true)
|
||||
})
|
||||
|
||||
QUnit.module('Assignment#isNonDefaultExternalTool', {
|
||||
setup() {
|
||||
fakeENV.setup({
|
||||
DEFAULT_ASSIGNMENT_TOOL_NAME: 'Default Tool',
|
||||
DEFAULT_ASSIGNMENT_TOOL_URL: 'https://www.test.com/blti'
|
||||
})
|
||||
},
|
||||
teardown() {
|
||||
fakeENV.teardown()
|
||||
}
|
||||
})
|
||||
|
||||
test('returns true if submissionType is "default_external_tool"', () => {
|
||||
const assignment = new Assignment({name: 'foo'})
|
||||
assignment.submissionTypes(['default_external_tool'])
|
||||
equal(assignment.isDefaultTool(), true)
|
||||
})
|
||||
|
||||
test('returns true when submissionType is "external_tool" and non default tool is selected', () => {
|
||||
const assignment = new Assignment({
|
||||
name: 'foo',
|
||||
external_tool_tag_attributes: {
|
||||
url: 'https://www.non-default.com/blti?foo'
|
||||
}
|
||||
})
|
||||
assignment.submissionTypes(['external_tool'])
|
||||
equal(assignment.isNonDefaultExternalTool(), true)
|
||||
})
|
||||
|
||||
test('returns true when submissionType is "external_tool"', () => {
|
||||
const assignment = new Assignment({name: 'foo'})
|
||||
assignment.submissionTypes(['external_tool'])
|
||||
equal(assignment.isNonDefaultExternalTool(), true)
|
||||
})
|
||||
|
||||
|
||||
QUnit.module('Assignment#isExternalTool')
|
||||
|
||||
test('returns true if record is external tool', () => {
|
||||
|
|
Loading…
Reference in New Issue