@@ -33,7 +33,6 @@ define(["require", "base/js/namespace", "base/js/dialog"], function (
3333 logo . width = "16" ;
3434 new_group . firstChild . prepend ( logo ) ;
3535 }
36-
3736 /**
3837 * Submit the currently open file to MarkUs. Relies on the following notebook metadata:
3938 * "markus": {
@@ -44,22 +43,28 @@ define(["require", "base/js/namespace", "base/js/dialog"], function (
4443 */
4544 function submitToMarkus ( ) {
4645 // Construct URL from MarkUs metadata
47- const markus_metadata = Jupyter . notebook . metadata . markus ;
48- let submitUrl ;
46+ const metadata = Jupyter . notebook . metadata ;
4947 try {
50- submitUrl = getSubmitUrl ( markus_metadata ) ;
48+ validateMetadata ( metadata ) ;
5149 } catch ( e ) {
5250 report_error ( e ) ;
5351 return ;
5452 }
5553
54+ const markusMetadata = metadata . markus ;
55+ const submitUrl = getSubmitUrl ( markusMetadata ) ;
56+
5657 // Get API key from file
57- const api_key_path = markus_metadata . api_key_path || DEFAULT_API_KEY_PATH ;
58+ const api_key_path = markusMetadata . api_key_path || DEFAULT_API_KEY_PATH ;
5859
59- fetchApiKey ( api_key_path ) . then (
60- ( apiKey ) => submitFile ( submitUrl , apiKey ) ,
61- report_error
62- ) ;
60+ fetchApiKey ( api_key_path )
61+ . then ( ( apiKey ) => {
62+ return submitFile ( submitUrl , apiKey ) ;
63+ } , report_error )
64+ . then (
65+ ( response ) => handleMarkUsResponse ( response , markusMetadata ) ,
66+ ( err ) => handleNetworkError ( submitUrl , err )
67+ ) ;
6368 }
6469
6570 /**
@@ -108,17 +113,51 @@ define(["require", "base/js/namespace", "base/js/dialog"], function (
108113
109114 /**
110115 * Constructs the MarkUs URL to submit the notebook file to.
111- * Raises an error if URL cannot be constructed from the metadata .
116+ * This assumes the metadata has already been validated .
112117 *
113118 * @param {object } markusMetadata
114119 * @returns {URL } the URL to submit to
115120 */
116121 function getSubmitUrl ( markusMetadata ) {
117- if ( markusMetadata === undefined ) {
122+ let { url, course_id, assessment_id } = markusMetadata ;
123+ return new URL (
124+ url +
125+ "/api/courses/" +
126+ course_id +
127+ "/assignments/" +
128+ assessment_id +
129+ "/submit_file"
130+ ) ;
131+ }
132+
133+ /**
134+ * Constructs the MarkUs URL to view an assessment (as a student).
135+ * This assumes the metadata has already been validated.
136+ *
137+ * @param {object } markusMetadata
138+ * @returns {URL } the assessment URL
139+ */
140+ function getAssessmentUrl ( markusMetadata ) {
141+ let { url, course_id, assessment_id } = markusMetadata ;
142+ return new URL (
143+ url + "/courses/" + course_id + "/assignments/" + assessment_id
144+ ) ;
145+ }
146+
147+ /**
148+ * Validate whether the given notebook metadata has a valid MarkUs configuration.
149+ * A successful return means the notebook metadata has a valid configuration.
150+ * An error is raised if the configuration is invalid.
151+ *
152+ * @param {object } metadata
153+ * @returns {null }
154+ */
155+ function validateMetadata ( metadata ) {
156+ if ( metadata [ "markus" ] === undefined ) {
118157 throw 'Notebook metadata is missing the "markus" key.' ;
119158 }
120159
121- let { url, course_id, assessment_id } = markusMetadata ;
160+ let { url, course_id, assessment_id } = metadata [ "markus" ] ;
122161
123162 if ( ! url || ! course_id || ! assessment_id ) {
124163 throw 'Notebook metadata is missing one or more of the following keys under "markus": "url", "course_id", or "assessment_id".' ;
@@ -131,28 +170,11 @@ define(["require", "base/js/namespace", "base/js/dialog"], function (
131170 throw 'Notebook metadata "course_id" and "assessment_id" values must be numbers.' ;
132171 }
133172
134- let submitUrl ;
135173 try {
136- submitUrl = new URL (
137- url +
138- "/api/courses/" +
139- course_id +
140- "/assignments/" +
141- assessment_id +
142- "/submit_file"
143- ) ;
174+ new URL ( url ) ;
144175 } catch {
145- throw (
146- "Notebook metatdata did not specify a valid MarkUs URL. Parameters: " +
147- JSON . stringify ( {
148- url : url ,
149- course_id : course_id ,
150- assessment_id : assessment_id ,
151- } )
152- ) ;
176+ throw 'Notebook metatdata "url" value did not specify a valid URL.' ;
153177 }
154-
155- return submitUrl ;
156178 }
157179
158180 /**
@@ -174,22 +196,24 @@ define(["require", "base/js/namespace", "base/js/dialog"], function (
174196 console . info (
175197 `markus-jupyter-extension: Submitting file ${ filename } to ${ submitUrl } `
176198 ) ;
177- fetch ( submitUrl , {
199+
200+ return fetch ( submitUrl , {
178201 method : "POST" ,
179202 headers : {
180203 AUTHORIZATION : "MarkUsAuth " + key ,
181204 Accept : "application/json" ,
182205 } ,
183206 body : formData ,
184- } ) . then ( handleMarkUsResponse , ( err ) => handleNetworkError ( submitUrl , err ) ) ;
207+ } ) ;
185208 }
186209
187210 /**
188211 * Handle MarkUs response after submitting file.
189212 * @param {* } response
213+ * @param {object } MarkUs metadata
190214 * @returns
191215 */
192- function handleMarkUsResponse ( response ) {
216+ function handleMarkUsResponse ( response , metadata ) {
193217 if ( ! response . ok ) {
194218 response . json ( ) . then ( ( body ) => {
195219 report_error (
@@ -200,10 +224,27 @@ define(["require", "base/js/namespace", "base/js/dialog"], function (
200224 return ;
201225 }
202226
227+ const assessmentUrl = getAssessmentUrl ( metadata ) ;
228+ const assessmentLink = document . createElement ( "a" ) ;
229+ assessmentLink . setAttribute ( "href" , assessmentUrl ) ;
230+ assessmentLink . setAttribute ( "target" , "_blank" ) ;
231+ assessmentLink . setAttribute ( "rel" , "noopener noreferrer" ) ;
232+ assessmentLink . innerText = assessmentUrl ;
233+ const body = document . createElement ( "p" ) ;
234+ body . appendChild (
235+ document . createTextNode ( "Your file has been submitted! Go to " )
236+ ) ;
237+ body . appendChild ( assessmentLink ) ;
238+ body . appendChild (
239+ document . createTextNode (
240+ " to view your submission. Please check your assessment due date carefully, as only files submitted before the due date will be graded."
241+ )
242+ ) ;
243+
203244 // Success dialog
204245 dialog . modal ( {
205- title : "Submit to MarkUs" ,
206- body : "Your file was submitted!" ,
246+ title : SUBMIT_LABEL ,
247+ body : body ,
207248 default_button : "Close" ,
208249 buttons : {
209250 Close : { } ,
@@ -233,7 +274,7 @@ define(["require", "base/js/namespace", "base/js/dialog"], function (
233274 function report_error ( msg ) {
234275 console . error ( msg ) ;
235276 dialog . modal ( {
236- title : "Submit to MarkUs" ,
277+ title : SUBMIT_LABEL ,
237278 body : "[ERROR] Could not submit file to MarkUs. Cause: " + msg ,
238279 default_button : "Close" ,
239280 buttons : {
0 commit comments