-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable canvas assignment creation API #3126
Changes from all commits
cdb1992
b5c730c
09e40eb
cbc33f9
0e6ba90
4422ddc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -13,6 +13,7 @@ import { contentItemForContent } from '../utils/content-item'; | |||||
* @prop {Record<string,string>} formFields - Form fields provided by the backend | ||||||
* that should be included in the response without any changes | ||||||
* @prop {string|null} groupSet | ||||||
* @prop {string|null} extLTIAssignmentId | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a good piece documentation somewhere in the backend code about what this parameter means? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's not super explicit block of docs just for this but it can followed from the schema: Line 99 in 4e26002
to the API view itself: lms/lms/views/api/assignments.py Line 22 in 4e26002
and in more detail on the comments on |
||||||
*/ | ||||||
|
||||||
/** | ||||||
|
@@ -34,9 +35,13 @@ export default function FilePickerFormFields({ | |||||
formFields, | ||||||
groupSet, | ||||||
ltiLaunchURL, | ||||||
extLTIAssignmentId, | ||||||
}) { | ||||||
/** @type {Record<string,string>} */ | ||||||
const extraParams = groupSet ? { group_set: groupSet } : {}; | ||||||
if (extLTIAssignmentId) { | ||||||
extraParams.ext_lti_assignment_id = extLTIAssignmentId; | ||||||
} | ||||||
marcospri marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
const contentItem = JSON.stringify( | ||||||
contentItemForContent(ltiLaunchURL, content, extraParams) | ||||||
); | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import { Config } from '../../config'; | |
import FilePickerApp, { $imports } from '../FilePickerApp'; | ||
import { checkAccessibility } from '../../../test-util/accessibility'; | ||
import mockImportedComponents from '../../../test-util/mock-imported-components'; | ||
import { waitFor, waitForElement } from '../../../test-util/wait'; | ||
|
||
function interact(wrapper, callback) { | ||
act(callback); | ||
|
@@ -31,6 +32,9 @@ describe('FilePickerApp', () => { | |
|
||
beforeEach(() => { | ||
fakeConfig = { | ||
api: { | ||
authToken: 'dummyAuthToken', | ||
}, | ||
filePicker: { | ||
formAction: 'https://www.shinylms.com/', | ||
formFields: { hidden_field: 'hidden_value' }, | ||
|
@@ -55,12 +59,18 @@ describe('FilePickerApp', () => { | |
/** | ||
* Check that the expected hidden form fields were set. | ||
*/ | ||
function checkFormFields(wrapper, expectedContent, expectedGroupSet) { | ||
function checkFormFields( | ||
wrapper, | ||
expectedContent, | ||
expectedGroupSet, | ||
expectedExtLTIAssignmentId | ||
) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since several of these parameters are often null, it may help to change to named arguments, via an object in future. function checkFormFields(wrapper, { content, groupSet, extLTIAssignmentId }) |
||
const formFields = wrapper.find('FilePickerFormFields'); | ||
assert.deepEqual(formFields.props(), { | ||
children: [], | ||
content: expectedContent, | ||
formFields: fakeConfig.filePicker.formFields, | ||
extLTIAssignmentId: expectedExtLTIAssignmentId, | ||
groupSet: expectedGroupSet, | ||
ltiLaunchURL: fakeConfig.filePicker.canvas.ltiLaunchUrl, | ||
}); | ||
|
@@ -104,6 +114,76 @@ describe('FilePickerApp', () => { | |
}); | ||
} | ||
|
||
context('when create assignment configuration is enabled', () => { | ||
const authURL = 'https://testlms.hypothes.is/authorize'; | ||
const createAssignmentPath = '/api/canvas/assignments'; | ||
let fakeAPICall; | ||
let fakeNewAssignment; | ||
|
||
beforeEach(() => { | ||
fakeConfig.filePicker.createAssignmentAPI = { | ||
marcospri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
authURL, | ||
path: createAssignmentPath, | ||
}; | ||
|
||
fakeAPICall = sinon.stub(); | ||
fakeNewAssignment = { ext_lti_assignment_id: 10 }; | ||
|
||
fakeAPICall | ||
.withArgs(sinon.match({ path: createAssignmentPath })) | ||
.resolves(fakeNewAssignment); | ||
|
||
$imports.$mock({ | ||
'../utils/api': { apiCall: fakeAPICall }, | ||
}); | ||
}); | ||
|
||
it('calls backend api when content is selected', async () => { | ||
const onSubmit = sinon.stub().callsFake(e => e.preventDefault()); | ||
const wrapper = renderFilePicker({ onSubmit }); | ||
|
||
selectContent(wrapper, 'https://example.com'); | ||
|
||
await waitFor(() => fakeAPICall.called); | ||
assert.calledWith(fakeAPICall, { | ||
authToken: 'dummyAuthToken', | ||
path: createAssignmentPath, | ||
data: { | ||
content: { type: 'url', url: 'https://example.com' }, | ||
groupset: null, | ||
}, | ||
}); | ||
|
||
await waitFor(() => onSubmit.called, 100); | ||
|
||
wrapper.update(); | ||
checkFormFields( | ||
wrapper, | ||
{ | ||
type: 'url', | ||
url: 'https://example.com', | ||
}, | ||
null /* groupSet */, | ||
fakeNewAssignment.ext_lti_assignment_id | ||
); | ||
}); | ||
|
||
it('shows an error if creating the assignment fails', async () => { | ||
const error = new Error('Something happened'); | ||
const onSubmit = sinon.stub().callsFake(e => e.preventDefault()); | ||
fakeAPICall | ||
.withArgs(sinon.match({ path: createAssignmentPath })) | ||
.rejects(error); | ||
|
||
const wrapper = renderFilePicker({ onSubmit }); | ||
|
||
selectContent(wrapper, 'https://example.com'); | ||
|
||
const errDialog = await waitForElement(wrapper, 'ErrorDialog'); | ||
assert.equal(errDialog.length, 1); | ||
assert.equal(errDialog.prop('error'), error); | ||
}); | ||
}); | ||
context('when groups are not enabled', () => { | ||
it('submits form when content is selected', () => { | ||
const onSubmit = sinon.stub().callsFake(e => e.preventDefault()); | ||
|
@@ -118,7 +198,8 @@ describe('FilePickerApp', () => { | |
type: 'url', | ||
url: 'https://example.com', | ||
}, | ||
null /* groupSet */ | ||
null /* groupSet */, | ||
null /* extLTIAssignmentId */ | ||
); | ||
}); | ||
|
||
|
@@ -217,7 +298,8 @@ describe('FilePickerApp', () => { | |
type: 'url', | ||
url: 'https://example.com', | ||
}, | ||
useGroupSet ? 'groupSet1' : null | ||
useGroupSet ? 'groupSet1' : null, | ||
null /* extLTIAssignmentId */ | ||
); | ||
}); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error is catched here and not in
createAssignment
to allow for this return and abort the submission.