Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit 4ce9f1c

Browse files
committed
💄 hparsons: Improve visualization for execution based feedback for hidden code
1 parent 26c67a8 commit 4ce9f1c

File tree

4 files changed

+150
-60
lines changed

4 files changed

+150
-60
lines changed

runestone/hparsons/hparsons.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ class HParsonsDirective(RunestoneIdDirective):
110110
block 3
111111
--hiddensuffix--
112112
// code that is for scaffolding unittest/execution (e.g. adding query for database)
113+
// most of the time the hiddensuffix is just "select * from table" to
114+
// get all entries from the table to test the update or other operations.
113115
--unittest--
114116
assert 1,1 == world
115117
assert 0,1 == hello

runestone/hparsons/js/SQLFeedback.js

Lines changed: 116 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ export default class SQLFeedback extends HParsonsFeedback {
135135
respDiv.parentElement.removeChild(respDiv);
136136
}
137137
$(this.output).text("");
138+
// creating new results div
139+
respDiv = document.createElement("div");
140+
respDiv.id = divid;
141+
this.outDiv.appendChild(respDiv);
142+
138143
// Run this query
139144
let query = await this.buildProg();
140145
if (!this.hparsons.db) {
@@ -144,68 +149,46 @@ export default class SQLFeedback extends HParsonsFeedback {
144149
return;
145150
}
146151

147-
// TODO: cancel the execution if previous steps have errors
152+
let executionSuccessFlag = true;
153+
154+
// executing hidden prefix if exist
148155
if (query.prefix) {
149156
this.prefixresults = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.prefix));
157+
if (this.prefixresults.at(-1).status == 'failure') {
158+
// if error occured in hidden prefix, log and stop executing the rest
159+
this.visualizeResults(respDiv, this.prefixresults, "Error executing hidden code in prefix");
160+
executionSuccessFlag = false;
161+
}
150162
}
151-
this.results = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.input));
152-
if (query.suffix) {
163+
164+
// executing student input in micro Parsons
165+
if (executionSuccessFlag) {
166+
this.results = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.input));
167+
// always render full execution results of student input regardless of success/failure
168+
this.visualizeResults(respDiv, this.results);
169+
if (this.results.at(-1).status == 'failure') {
170+
// if error occured in student input, stop executing suffix/unitttest
171+
executionSuccessFlag = false;
172+
}
173+
}
174+
175+
// executing hidden suffix if exist
176+
// In most cases the suffix is just "select * from x" to
177+
// see if the operations on the database is correct
178+
if (executionSuccessFlag && query.suffix) {
153179
this.suffixresults = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.suffix));
180+
if (this.suffixresults.at(-1).status == 'failure') {
181+
// if error occured in hidden suffix, visualize the results
182+
this.visualizeResults(respDiv, this.suffixresults, "Error executing hidden code in suffix");
183+
}
154184
}
155185

156-
respDiv = document.createElement("div");
157-
respDiv.id = divid;
158-
this.outDiv.appendChild(respDiv);
186+
// show the output div
159187
$(this.outDiv).show();
160-
// Sometimes we don't want to show a bunch of intermediate results
161-
// like when we are including a bunch of previous statements from
162-
// other activecodes In that case the showlastsql flag can be set
163-
// so we only show the last result
164-
let resultArray = this.results;
165-
for (let r of resultArray) {
166-
let section = document.createElement("div");
167-
section.setAttribute("class", "hp_sql_result");
168-
respDiv.appendChild(section);
169-
if (r.status === "success") {
170-
if (r.columns) {
171-
let tableDiv = document.createElement("div");
172-
section.appendChild(tableDiv);
173-
let maxHeight = 350;
174-
if (resultArray.length > 1) maxHeight = 200; // max height smaller if lots of results
175-
createTable(r, tableDiv, maxHeight);
176-
let messageBox = document.createElement("pre");
177-
let rmsg = r.rowcount !== 1 ? " rows " : " row ";
178-
let msg = "" + r.rowcount + rmsg + "returned";
179-
if (r.rowcount > 100) {
180-
msg = msg + " (only first 100 rows displayed)";
181-
}
182-
msg = msg + ".";
183-
messageBox.textContent = msg;
184-
messageBox.setAttribute("class", "hp_sql_result_success");
185-
section.appendChild(messageBox);
186-
} else if (r.rowcount) {
187-
let messageBox = document.createElement("pre");
188-
let op = r.operation;
189-
op = op + (op.charAt(op.length - 1) === "e" ? "d." : "ed.");
190-
let rmsg = r.rowcount !== 1 ? " rows " : " row ";
191-
messageBox.textContent = "" + r.rowcount + rmsg + op;
192-
messageBox.setAttribute("class", "hp_sql_result_success");
193-
section.appendChild(messageBox);
194-
} else {
195-
let messageBox = document.createElement("pre");
196-
messageBox.textContent = "Operation succeeded.";
197-
messageBox.setAttribute("class", "hp_sql_result_success");
198-
section.appendChild(messageBox);
199-
}
200-
} else {
201-
let messageBox = document.createElement("pre");
202-
messageBox.textContent = r.message;
203-
messageBox.setAttribute("class", "hp_sql_result_failure");
204-
section.appendChild(messageBox);
205-
}
206-
}
207188

208189
// Now handle autograding
190+
// autograding takes the results of the hidden suffix if exist
191+
// otherwise take the result of student input
209192
if (this.hparsons.unittest) {
210193
if (this.suffixresults) {
211194
this.testResult = this.autograde(
@@ -223,6 +206,26 @@ export default class SQLFeedback extends HParsonsFeedback {
223206
return Promise.resolve("done");
224207
}
225208

209+
// Refactored from activecode-sql.
210+
// Takes iterated statements from db.iterateStatemnts(queryString)
211+
// Returns Array<result>:
212+
/* each result: {
213+
status: "success" or "faliure",
214+
// for SELECT statements (?):
215+
columns: number of columns,
216+
values: data,
217+
rowcount: number of rows in data,
218+
// for INSERT, UPDATE, DELETE:
219+
operation: "INSERT", "UPDATE", or "DELETE",
220+
rowcount: number of rows modified,
221+
// when error occurred (aside from status):
222+
message: error message,
223+
sql: remaining SQL (?)
224+
// when no queries were executed:
225+
message: "no queries submitted"
226+
}*/
227+
// If an error occurs it will stop executing the rest of queries in it.
228+
// Thus the error result will always be the last item.
226229
executeIteratedStatements(it) {
227230
let results = [];
228231
try {
@@ -278,6 +281,66 @@ export default class SQLFeedback extends HParsonsFeedback {
278281
}
279282
return results;
280283
}
284+
285+
// output the results in the resultArray(Array<results>).
286+
// container: the container that contains the results
287+
// resultArray (Array<result>): see executeIteratedStatements
288+
// Each result will be in a separate row.
289+
// devNote will be displayed in the top row if exist.
290+
// Current usage: "error executing hidden code in prefix/suffix"
291+
visualizeResults(container, resultArray, devNote) {
292+
if (devNote) {
293+
let section = document.createElement("div");
294+
section.setAttribute("class", "hp_sql_result");
295+
container.appendChild(section);
296+
let messageBox = document.createElement("pre");
297+
messageBox.textContent = devNote;
298+
messageBox.setAttribute("class", "hp_sql_result_failure");
299+
section.appendChild(messageBox);
300+
}
301+
for (let r of resultArray) {
302+
let section = document.createElement("div");
303+
section.setAttribute("class", "hp_sql_result");
304+
container.appendChild(section);
305+
if (r.status === "success") {
306+
if (r.columns) {
307+
let tableDiv = document.createElement("div");
308+
section.appendChild(tableDiv);
309+
let maxHeight = 350;
310+
if (resultArray.length > 1) maxHeight = 200; // max height smaller if lots of results
311+
createTable(r, tableDiv, maxHeight);
312+
let messageBox = document.createElement("pre");
313+
let rmsg = r.rowcount !== 1 ? " rows " : " row ";
314+
let msg = "" + r.rowcount + rmsg + "returned";
315+
if (r.rowcount > 100) {
316+
msg = msg + " (only first 100 rows displayed)";
317+
}
318+
msg = msg + ".";
319+
messageBox.textContent = msg;
320+
messageBox.setAttribute("class", "hp_sql_result_success");
321+
section.appendChild(messageBox);
322+
} else if (r.rowcount) {
323+
let messageBox = document.createElement("pre");
324+
let op = r.operation;
325+
op = op + (op.charAt(op.length - 1) === "e" ? "d." : "ed.");
326+
let rmsg = r.rowcount !== 1 ? " rows " : " row ";
327+
messageBox.textContent = "" + r.rowcount + rmsg + op;
328+
messageBox.setAttribute("class", "hp_sql_result_success");
329+
section.appendChild(messageBox);
330+
} else {
331+
let messageBox = document.createElement("pre");
332+
messageBox.textContent = "Operation succeeded.";
333+
messageBox.setAttribute("class", "hp_sql_result_success");
334+
section.appendChild(messageBox);
335+
}
336+
} else {
337+
let messageBox = document.createElement("pre");
338+
messageBox.textContent = r.message;
339+
messageBox.setAttribute("class", "hp_sql_result_failure");
340+
section.appendChild(messageBox);
341+
}
342+
}
343+
}
281344

282345
// adapted from activecode
283346
async buildProg() {

runestone/hparsons/js/hparsons.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,6 @@ export default class HParsons extends RunestoneBase {
6868
this.originalBlocks = this.processSingleContent(code, '--blocks--').split('\n').slice(1,-1);
6969
this.hiddenSuffix = this.processSingleContent(code, '--hiddensuffix--');
7070
this.unittest = this.processSingleContent(code, '--unittest--');
71-
console.log({
72-
'pre': this.hiddenPrefix,
73-
'blocks': this.originalBlocks,
74-
'suf': this.hiddenSuffix,
75-
'test': this.unittest
76-
})
7771
}
7872

7973
processSingleContent(code, delimitier) {

runestone/hparsons/test/_sources/index.rst

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ Reusable Block with Execution Based Feedback
105105

106106
Randomized Block with Execution Based Feedback and Hidden Code
107107
---------------------------------------------------------------
108-
.. hparsons:: hparsons_lg_sql_practice_A_2_pb
108+
.. hparsons:: test_hparsons_sql_exe_hidden
109109
:language: sql
110110
:randomize:
111111

@@ -116,6 +116,10 @@ Randomized Block with Execution Based Feedback and Hidden Code
116116
A student completed an extra assignment and got some additional points.
117117

118118
Please write an UPDATE statement to change the entry whose ``student_id`` is 1, and set their math score for ``final`` ``test_name`` to 90.
119+
120+
hidden prefix initializes the table above;
121+
hidden suffix is "SELECT * FROM grades".
122+
119123
~~~~
120124
--hiddenprefix--
121125
DROP TABLE IF EXISTS grades;
@@ -140,3 +144,30 @@ Randomized Block with Execution Based Feedback and Hidden Code
140144
assert 1,1 == final
141145
assert 1,3 == 90
142146
assert 3,3 == 99
147+
148+
149+
Randomized Block with Execution Based Feedback and Hidden Code + error in prefix
150+
--------------------------------------------------------------------------------
151+
.. hparsons:: test_hparsons_sql_exe_hidden_error
152+
:language: sql
153+
:randomize:
154+
155+
The third line of the hidden code is incorrect.
156+
157+
~~~~
158+
--hiddenprefix--
159+
DROP TABLE IF EXISTS grades;
160+
create table "grades" ("student_id" INTEGER, "test_name" TEXT, "english" INTEGER, "math" INTEGER);
161+
INSERT INTO grades (student_id,test_name,english,math)
162+
--blocks--
163+
UPDATE grades
164+
SET
165+
math = 90
166+
WHERE
167+
student_id = 1 AND test_name = "final"
168+
LET
169+
student_id = 1 AND test_name = final
170+
--unittest--
171+
assert 1,1 == final
172+
assert 1,3 == 90
173+
assert 3,3 == 99

0 commit comments

Comments
 (0)