From 7ad7e0ac797e7c132714fcdeed662af5b781a16f Mon Sep 17 00:00:00 2001 From: Zihan Wu Date: Wed, 25 Jan 2023 14:59:15 +0800 Subject: [PATCH 1/7] :recycle: Refactor getting settings from code --- runestone/hparsons/js/SQLFeedback.js | 4 +-- runestone/hparsons/js/hparsons.js | 47 +++++++++++++--------------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/runestone/hparsons/js/SQLFeedback.js b/runestone/hparsons/js/SQLFeedback.js index dcc683849..cd3df2a2e 100644 --- a/runestone/hparsons/js/SQLFeedback.js +++ b/runestone/hparsons/js/SQLFeedback.js @@ -247,7 +247,7 @@ export default class SQLFeedback extends HParsonsFeedback { } // Now handle autograding - if (this.hparsons.suffix) { + if (this.hparsons.unittest) { this.testResult = this.autograde( this.results[this.results.length - 1] ); @@ -291,7 +291,7 @@ export default class SQLFeedback extends HParsonsFeedback { // might move to base class if used by multiple execution based feedback autograde(result_table) { - var tests = this.hparsons.suffix.split(/\n/); + var tests = this.hparsons.unittest.split(/\n/); this.passed = 0; this.failed = 0; // Tests should be of the form diff --git a/runestone/hparsons/js/hparsons.js b/runestone/hparsons/js/hparsons.js index ba67c502f..d4da5cc21 100644 --- a/runestone/hparsons/js/hparsons.js +++ b/runestone/hparsons/js/hparsons.js @@ -14,8 +14,6 @@ if (hpList === undefined) hpList = {}; export default class HParsons extends RunestoneBase { constructor(opts) { super(opts); - // copied from activecode - var suffStart; // getting settings var orig = $(opts.orig).find("textarea")[0]; this.reuse = $(orig).data("reuse") ? true : false; @@ -37,16 +35,7 @@ export default class HParsons extends RunestoneBase { this.loadButton = null; this.outerDiv = null; this.controlDiv = null; - let prefixEnd = this.code.indexOf("^^^^"); - if (prefixEnd > -1) { - this.prefix = this.code.substring(0, prefixEnd); - this.code = this.code.substring(prefixEnd + 5); - } - suffStart = this.code.indexOf("--unittest--"); - if (suffStart > -1) { - this.suffix = this.code.substring(suffStart + 5); - this.code = this.code.substring(0, suffStart); - } + this.processContent(this.code) // Change to factory when more execution based feedback is included if (this.isBlockGrading) { @@ -73,6 +62,26 @@ export default class HParsons extends RunestoneBase { this.feedbackController.init(); } + processContent(code) { + // todo: add errors when blocks are nonexistent (maybe in python)? + this.originalBlocks = this.processSingleContent(code, '--blocks--').split('\n').slice(1,-1); + this.unittest = this.processSingleContent(code, '--unittest--'); + } + + processSingleContent(code, delimitier) { + let index = code.indexOf(delimitier); + if (index > -1) { + let content = code.substring(index + delimitier.length); + let endIndex = content.indexOf("\n--"); + content = + endIndex > -1 + ? content.substring(0, endIndex + 1) + : content; + return content; + } + return undefined; + } + // copied from activecode, already modified to add parsons createEditor() { this.outerDiv = document.createElement("div"); @@ -82,24 +91,12 @@ export default class HParsons extends RunestoneBase { this.logHorizontalParsonsEvent(ev.detail); this.feedbackController.clearFeedback(); }); - let blocks = []; - let blockIndex = this.code.indexOf("--blocks--"); - if (blockIndex > -1) { - let blocksString = this.code.substring(blockIndex + 10); - let endIndex = blocksString.indexOf("\n--"); - blocksString = - endIndex > -1 - ? blocksString.substring(0, endIndex) - : blocksString; - blocks = blocksString.split("\n"); - } - this.originalBlocks = blocks.slice(1, -1); const props = { selector: `#${this.divid}-container`, id: `${this.divid}-hparsons`, reuse: this.reuse, randomize: this.randomize, - parsonsBlocks: blocks.slice(1, -1), + parsonsBlocks: [...this.originalBlocks], language: this.language } InitMicroParsons(props); From 26c67a84340e0e1f968e2595f5843d973afcbf75 Mon Sep 17 00:00:00 2001 From: Zihan Wu Date: Wed, 25 Jan 2023 16:11:05 +0800 Subject: [PATCH 2/7] :sparkles: micro Parsons: Allow executing hidden prefix and sufix for SQL --- runestone/hparsons/hparsons.py | 4 + runestone/hparsons/js/SQLFeedback.js | 148 ++++++++++++--------- runestone/hparsons/js/hparsons.js | 8 ++ runestone/hparsons/test/_sources/index.rst | 39 ++++++ 4 files changed, 136 insertions(+), 63 deletions(-) diff --git a/runestone/hparsons/hparsons.py b/runestone/hparsons/hparsons.py index 1971d288d..f8bd3124f 100755 --- a/runestone/hparsons/hparsons.py +++ b/runestone/hparsons/hparsons.py @@ -102,10 +102,14 @@ class HParsonsDirective(RunestoneIdDirective): Here is the problem description. It must ends with the tildes. Make sure you use the correct delimitier for each section below. ~~~~ + --hiddenprefix-- + // code that is for scaffolding the execution (e.g. initializing database) --blocks-- block 1 block 2 block 3 + --hiddensuffix-- + // code that is for scaffolding unittest/execution (e.g. adding query for database) --unittest-- assert 1,1 == world assert 0,1 == hello diff --git a/runestone/hparsons/js/SQLFeedback.js b/runestone/hparsons/js/SQLFeedback.js index cd3df2a2e..1ac990618 100644 --- a/runestone/hparsons/js/SQLFeedback.js +++ b/runestone/hparsons/js/SQLFeedback.js @@ -45,16 +45,21 @@ export default class SQLFeedback extends HParsonsFeedback { // fnprefix sets the path to load the sql-wasm.wasm file var bookprefix; var fnprefix; - if (eBookConfig.useRunestoneServices) { + if ( + eBookConfig.useRunestoneServices || + window.location.search.includes("mode=browsing") + ) { bookprefix = `${eBookConfig.app}/books/published/${eBookConfig.basecourse}`; fnprefix = bookprefix + "/_static"; } else { + // The else clause handles the case where you are building for a static web browser bookprefix = ""; fnprefix = "/_static"; } let SQLconfig = { locateFile: (filename) => `${fnprefix}/${filename}`, }; + // this.showLast = $(this.origElem).data("showlastsql"); var self = this.hparsons; initSqlJs(SQLconfig).then(function (SQL) { // set up call to load database asynchronously if given @@ -134,64 +139,18 @@ export default class SQLFeedback extends HParsonsFeedback { let query = await this.buildProg(); if (!this.hparsons.db) { $(this.output).text( - `Error: Database not initialized! DBURL: ${this.dburl}` + `Error: Database not initialized! DBURL: ${this.hparsons.dburl}` ); return; } - let it = this.hparsons.db.iterateStatements(query); - this.results = []; - try { - for (let statement of it) { - let columns = statement.getColumnNames(); - if (columns.length > 0) { - // data! probably a SELECT - let data = []; - while (statement.step()) { - data.push(statement.get()); - } - this.results.push({ - status: "success", - columns: columns, - values: data, - rowcount: data.length, - }); - } else { - let nsql = statement.getNormalizedSQL(); - let prefix = nsql.substr(0, 6).toLowerCase(); - statement.step(); // execute the query - // Try to detect INSERT/UPDATE/DELETE to give friendly feedback - // on rows modified - unfortunately, this won't catch such queries - // if they use CTEs. There seems to be no reliable way of knowing - // when a SQLite query actually modified data. - if ( - prefix === "insert" || - prefix === "update" || - prefix === "delete" - ) { - this.results.push({ - status: "success", - operation: prefix, - rowcount: this.db.getRowsModified(), - }); - } else { - this.results.push({ status: "success" }); - } - } - } - } catch (e) { - this.results.push({ - status: "failure", - message: e.toString(), - sql: it.getRemainingSQL(), - }); + // TODO: cancel the execution if previous steps have errors + if (query.prefix) { + this.prefixresults = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.prefix)); } - - if (this.results.length === 0) { - this.results.push({ - status: "failure", - message: "No queries submitted.", - }); + this.results = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.input)); + if (query.suffix) { + this.suffixresults = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.suffix)); } respDiv = document.createElement("div"); @@ -248,9 +207,15 @@ export default class SQLFeedback extends HParsonsFeedback { // Now handle autograding if (this.hparsons.unittest) { - this.testResult = this.autograde( - this.results[this.results.length - 1] - ); + if (this.suffixresults) { + this.testResult = this.autograde( + this.suffixresults[this.suffixresults.length - 1] + ); + } else { + this.testResult = this.autograde( + this.results[this.results.length - 1] + ); + } } else { $(this.output).css("visibility", "hidden"); } @@ -258,15 +223,72 @@ export default class SQLFeedback extends HParsonsFeedback { return Promise.resolve("done"); } + executeIteratedStatements(it) { + let results = []; + try { + for (let statement of it) { + let columns = statement.getColumnNames(); + if (columns.length > 0) { + // data! probably a SELECT + let data = []; + while (statement.step()) { + data.push(statement.get()); + } + results.push({ + status: "success", + columns: columns, + values: data, + rowcount: data.length, + }); + } else { + let nsql = statement.getNormalizedSQL(); + let prefix = nsql.substr(0, 6).toLowerCase(); + statement.step(); // execute the query + // Try to detect INSERT/UPDATE/DELETE to give friendly feedback + // on rows modified - unfortunately, this won't catch such queries + // if they use CTEs. There seems to be no reliable way of knowing + // when a SQLite query actually modified data. + if ( + prefix === "insert" || + prefix === "update" || + prefix === "delete" + ) { + results.push({ + status: "success", + operation: prefix, + rowcount: this.hparsons.db.getRowsModified(), + }); + } else { + results.push({ status: "success" }); + } + } + } + } catch (e) { + results.push({ + status: "failure", + message: e.toString(), + sql: it.getRemainingSQL(), + }); + } + if (results.length === 0) { + results.push({ + status: "failure", + message: "No queries submitted.", + }); + } + return results; + } + // adapted from activecode async buildProg() { // assemble code from prefix, suffix, and editor for running. - // TODO: fix or remove text entry - var prog; - if (this.hparsons.textentry) { - prog = this.hparsons.hparsonsInput.getCurrentInput(); - } else { - prog = this.hparsons.hparsonsInput.getParsonsTextArray().join(' ') + "\n"; + let prog = {}; + if (this.hparsons.hiddenPrefix) { + prog.prefix = this.hparsons.hiddenPrefix; + } + prog.input = this.hparsons.hparsonsInput.getParsonsTextArray().join(' ') + "\n"; + if (this.hparsons.hiddenSuffix) { + prog.suffix = this.hparsons.hiddenSuffix; } return Promise.resolve(prog); } diff --git a/runestone/hparsons/js/hparsons.js b/runestone/hparsons/js/hparsons.js index d4da5cc21..093ea94a4 100644 --- a/runestone/hparsons/js/hparsons.js +++ b/runestone/hparsons/js/hparsons.js @@ -64,8 +64,16 @@ export default class HParsons extends RunestoneBase { processContent(code) { // todo: add errors when blocks are nonexistent (maybe in python)? + this.hiddenPrefix = this.processSingleContent(code, '--hiddenprefix--'); this.originalBlocks = this.processSingleContent(code, '--blocks--').split('\n').slice(1,-1); + this.hiddenSuffix = this.processSingleContent(code, '--hiddensuffix--'); this.unittest = this.processSingleContent(code, '--unittest--'); + console.log({ + 'pre': this.hiddenPrefix, + 'blocks': this.originalBlocks, + 'suf': this.hiddenSuffix, + 'test': this.unittest + }) } processSingleContent(code, delimitier) { diff --git a/runestone/hparsons/test/_sources/index.rst b/runestone/hparsons/test/_sources/index.rst index cfa01fdeb..c22d5fc1b 100644 --- a/runestone/hparsons/test/_sources/index.rst +++ b/runestone/hparsons/test/_sources/index.rst @@ -101,3 +101,42 @@ Reusable Block with Execution Based Feedback assert 1,1 == world assert 0,1 == hello assert 2,1 == 42 + + +Randomized Block with Execution Based Feedback and Hidden Code +--------------------------------------------------------------- +.. hparsons:: hparsons_lg_sql_practice_A_2_pb + :language: sql + :randomize: + + In the ``grades`` table: + + .. image:: https://i.ibb.co/r6qShy5/practice-grade.png + + A student completed an extra assignment and got some additional points. + + 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. + ~~~~ + --hiddenprefix-- + DROP TABLE IF EXISTS grades; + create table "grades" ("student_id" INTEGER, "test_name" TEXT, "english" INTEGER, "math" INTEGER); + INSERT INTO grades (student_id,test_name,english,math) VALUES + ('1', 'midterm', 62, 84), + ('1', 'final', 70, 86), + ('2', 'midterm', 50, 95), + ('2', 'final', 80, 99), + ('3', 'midterm', 55, 91); + --blocks-- + UPDATE grades + SET + math = 90 + WHERE + student_id = 1 AND test_name = "final" + LET + student_id = 1 AND test_name = final + --hiddensuffix-- + ;SELECT * FROM grades + --unittest-- + assert 1,1 == final + assert 1,3 == 90 + assert 3,3 == 99 From 4ce9f1ce591bc8228f9f61bcb4eadd20443c92a5 Mon Sep 17 00:00:00 2001 From: Zihan Wu Date: Thu, 26 Jan 2023 16:39:49 +0800 Subject: [PATCH 3/7] :lipstick: hparsons: Improve visualization for execution based feedback for hidden code --- runestone/hparsons/hparsons.py | 2 + runestone/hparsons/js/SQLFeedback.js | 169 ++++++++++++++------- runestone/hparsons/js/hparsons.js | 6 - runestone/hparsons/test/_sources/index.rst | 33 +++- 4 files changed, 150 insertions(+), 60 deletions(-) diff --git a/runestone/hparsons/hparsons.py b/runestone/hparsons/hparsons.py index f8bd3124f..32661962f 100755 --- a/runestone/hparsons/hparsons.py +++ b/runestone/hparsons/hparsons.py @@ -110,6 +110,8 @@ class HParsonsDirective(RunestoneIdDirective): block 3 --hiddensuffix-- // code that is for scaffolding unittest/execution (e.g. adding query for database) + // most of the time the hiddensuffix is just "select * from table" to + // get all entries from the table to test the update or other operations. --unittest-- assert 1,1 == world assert 0,1 == hello diff --git a/runestone/hparsons/js/SQLFeedback.js b/runestone/hparsons/js/SQLFeedback.js index 1ac990618..25a92a877 100644 --- a/runestone/hparsons/js/SQLFeedback.js +++ b/runestone/hparsons/js/SQLFeedback.js @@ -135,6 +135,11 @@ export default class SQLFeedback extends HParsonsFeedback { respDiv.parentElement.removeChild(respDiv); } $(this.output).text(""); + // creating new results div + respDiv = document.createElement("div"); + respDiv.id = divid; + this.outDiv.appendChild(respDiv); + // Run this query let query = await this.buildProg(); if (!this.hparsons.db) { @@ -144,68 +149,46 @@ export default class SQLFeedback extends HParsonsFeedback { return; } - // TODO: cancel the execution if previous steps have errors + let executionSuccessFlag = true; + + // executing hidden prefix if exist if (query.prefix) { this.prefixresults = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.prefix)); + if (this.prefixresults.at(-1).status == 'failure') { + // if error occured in hidden prefix, log and stop executing the rest + this.visualizeResults(respDiv, this.prefixresults, "Error executing hidden code in prefix"); + executionSuccessFlag = false; + } } - this.results = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.input)); - if (query.suffix) { + + // executing student input in micro Parsons + if (executionSuccessFlag) { + this.results = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.input)); + // always render full execution results of student input regardless of success/failure + this.visualizeResults(respDiv, this.results); + if (this.results.at(-1).status == 'failure') { + // if error occured in student input, stop executing suffix/unitttest + executionSuccessFlag = false; + } + } + + // executing hidden suffix if exist + // In most cases the suffix is just "select * from x" to + // see if the operations on the database is correct + if (executionSuccessFlag && query.suffix) { this.suffixresults = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.suffix)); + if (this.suffixresults.at(-1).status == 'failure') { + // if error occured in hidden suffix, visualize the results + this.visualizeResults(respDiv, this.suffixresults, "Error executing hidden code in suffix"); + } } - respDiv = document.createElement("div"); - respDiv.id = divid; - this.outDiv.appendChild(respDiv); + // show the output div $(this.outDiv).show(); - // Sometimes we don't want to show a bunch of intermediate results - // like when we are including a bunch of previous statements from - // other activecodes In that case the showlastsql flag can be set - // so we only show the last result - let resultArray = this.results; - for (let r of resultArray) { - let section = document.createElement("div"); - section.setAttribute("class", "hp_sql_result"); - respDiv.appendChild(section); - if (r.status === "success") { - if (r.columns) { - let tableDiv = document.createElement("div"); - section.appendChild(tableDiv); - let maxHeight = 350; - if (resultArray.length > 1) maxHeight = 200; // max height smaller if lots of results - createTable(r, tableDiv, maxHeight); - let messageBox = document.createElement("pre"); - let rmsg = r.rowcount !== 1 ? " rows " : " row "; - let msg = "" + r.rowcount + rmsg + "returned"; - if (r.rowcount > 100) { - msg = msg + " (only first 100 rows displayed)"; - } - msg = msg + "."; - messageBox.textContent = msg; - messageBox.setAttribute("class", "hp_sql_result_success"); - section.appendChild(messageBox); - } else if (r.rowcount) { - let messageBox = document.createElement("pre"); - let op = r.operation; - op = op + (op.charAt(op.length - 1) === "e" ? "d." : "ed."); - let rmsg = r.rowcount !== 1 ? " rows " : " row "; - messageBox.textContent = "" + r.rowcount + rmsg + op; - messageBox.setAttribute("class", "hp_sql_result_success"); - section.appendChild(messageBox); - } else { - let messageBox = document.createElement("pre"); - messageBox.textContent = "Operation succeeded."; - messageBox.setAttribute("class", "hp_sql_result_success"); - section.appendChild(messageBox); - } - } else { - let messageBox = document.createElement("pre"); - messageBox.textContent = r.message; - messageBox.setAttribute("class", "hp_sql_result_failure"); - section.appendChild(messageBox); - } - } // Now handle autograding + // autograding takes the results of the hidden suffix if exist + // otherwise take the result of student input if (this.hparsons.unittest) { if (this.suffixresults) { this.testResult = this.autograde( @@ -223,6 +206,26 @@ export default class SQLFeedback extends HParsonsFeedback { return Promise.resolve("done"); } + // Refactored from activecode-sql. + // Takes iterated statements from db.iterateStatemnts(queryString) + // Returns Array: + /* each result: { + status: "success" or "faliure", + // for SELECT statements (?): + columns: number of columns, + values: data, + rowcount: number of rows in data, + // for INSERT, UPDATE, DELETE: + operation: "INSERT", "UPDATE", or "DELETE", + rowcount: number of rows modified, + // when error occurred (aside from status): + message: error message, + sql: remaining SQL (?) + // when no queries were executed: + message: "no queries submitted" + }*/ + // If an error occurs it will stop executing the rest of queries in it. + // Thus the error result will always be the last item. executeIteratedStatements(it) { let results = []; try { @@ -278,6 +281,66 @@ export default class SQLFeedback extends HParsonsFeedback { } return results; } + + // output the results in the resultArray(Array). + // container: the container that contains the results + // resultArray (Array): see executeIteratedStatements + // Each result will be in a separate row. + // devNote will be displayed in the top row if exist. + // Current usage: "error executing hidden code in prefix/suffix" + visualizeResults(container, resultArray, devNote) { + if (devNote) { + let section = document.createElement("div"); + section.setAttribute("class", "hp_sql_result"); + container.appendChild(section); + let messageBox = document.createElement("pre"); + messageBox.textContent = devNote; + messageBox.setAttribute("class", "hp_sql_result_failure"); + section.appendChild(messageBox); + } + for (let r of resultArray) { + let section = document.createElement("div"); + section.setAttribute("class", "hp_sql_result"); + container.appendChild(section); + if (r.status === "success") { + if (r.columns) { + let tableDiv = document.createElement("div"); + section.appendChild(tableDiv); + let maxHeight = 350; + if (resultArray.length > 1) maxHeight = 200; // max height smaller if lots of results + createTable(r, tableDiv, maxHeight); + let messageBox = document.createElement("pre"); + let rmsg = r.rowcount !== 1 ? " rows " : " row "; + let msg = "" + r.rowcount + rmsg + "returned"; + if (r.rowcount > 100) { + msg = msg + " (only first 100 rows displayed)"; + } + msg = msg + "."; + messageBox.textContent = msg; + messageBox.setAttribute("class", "hp_sql_result_success"); + section.appendChild(messageBox); + } else if (r.rowcount) { + let messageBox = document.createElement("pre"); + let op = r.operation; + op = op + (op.charAt(op.length - 1) === "e" ? "d." : "ed."); + let rmsg = r.rowcount !== 1 ? " rows " : " row "; + messageBox.textContent = "" + r.rowcount + rmsg + op; + messageBox.setAttribute("class", "hp_sql_result_success"); + section.appendChild(messageBox); + } else { + let messageBox = document.createElement("pre"); + messageBox.textContent = "Operation succeeded."; + messageBox.setAttribute("class", "hp_sql_result_success"); + section.appendChild(messageBox); + } + } else { + let messageBox = document.createElement("pre"); + messageBox.textContent = r.message; + messageBox.setAttribute("class", "hp_sql_result_failure"); + section.appendChild(messageBox); + } + } + } // adapted from activecode async buildProg() { diff --git a/runestone/hparsons/js/hparsons.js b/runestone/hparsons/js/hparsons.js index 093ea94a4..4b5f79659 100644 --- a/runestone/hparsons/js/hparsons.js +++ b/runestone/hparsons/js/hparsons.js @@ -68,12 +68,6 @@ export default class HParsons extends RunestoneBase { this.originalBlocks = this.processSingleContent(code, '--blocks--').split('\n').slice(1,-1); this.hiddenSuffix = this.processSingleContent(code, '--hiddensuffix--'); this.unittest = this.processSingleContent(code, '--unittest--'); - console.log({ - 'pre': this.hiddenPrefix, - 'blocks': this.originalBlocks, - 'suf': this.hiddenSuffix, - 'test': this.unittest - }) } processSingleContent(code, delimitier) { diff --git a/runestone/hparsons/test/_sources/index.rst b/runestone/hparsons/test/_sources/index.rst index c22d5fc1b..6ffa966cd 100644 --- a/runestone/hparsons/test/_sources/index.rst +++ b/runestone/hparsons/test/_sources/index.rst @@ -105,7 +105,7 @@ Reusable Block with Execution Based Feedback Randomized Block with Execution Based Feedback and Hidden Code --------------------------------------------------------------- -.. hparsons:: hparsons_lg_sql_practice_A_2_pb +.. hparsons:: test_hparsons_sql_exe_hidden :language: sql :randomize: @@ -116,6 +116,10 @@ Randomized Block with Execution Based Feedback and Hidden Code A student completed an extra assignment and got some additional points. 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. + + hidden prefix initializes the table above; + hidden suffix is "SELECT * FROM grades". + ~~~~ --hiddenprefix-- DROP TABLE IF EXISTS grades; @@ -140,3 +144,30 @@ Randomized Block with Execution Based Feedback and Hidden Code assert 1,1 == final assert 1,3 == 90 assert 3,3 == 99 + + +Randomized Block with Execution Based Feedback and Hidden Code + error in prefix +-------------------------------------------------------------------------------- +.. hparsons:: test_hparsons_sql_exe_hidden_error + :language: sql + :randomize: + + The third line of the hidden code is incorrect. + + ~~~~ + --hiddenprefix-- + DROP TABLE IF EXISTS grades; + create table "grades" ("student_id" INTEGER, "test_name" TEXT, "english" INTEGER, "math" INTEGER); + INSERT INTO grades (student_id,test_name,english,math) + --blocks-- + UPDATE grades + SET + math = 90 + WHERE + student_id = 1 AND test_name = "final" + LET + student_id = 1 AND test_name = final + --unittest-- + assert 1,1 == final + assert 1,3 == 90 + assert 3,3 == 99 From 25bfa1854c1a149cc2ef67f2633bc55cab1e24c8 Mon Sep 17 00:00:00 2001 From: Zihan Wu Date: Thu, 26 Jan 2023 18:54:13 +0800 Subject: [PATCH 4/7] :bug: Fix horizontal Parsons execution based feedback result area does not autosize --- runestone/hparsons/js/SQLFeedback.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/runestone/hparsons/js/SQLFeedback.js b/runestone/hparsons/js/SQLFeedback.js index 25a92a877..db1cd1dab 100644 --- a/runestone/hparsons/js/SQLFeedback.js +++ b/runestone/hparsons/js/SQLFeedback.js @@ -139,6 +139,8 @@ export default class SQLFeedback extends HParsonsFeedback { respDiv = document.createElement("div"); respDiv.id = divid; this.outDiv.appendChild(respDiv); + // show the output div + $(this.outDiv).show(); // Run this query let query = await this.buildProg(); @@ -183,9 +185,6 @@ export default class SQLFeedback extends HParsonsFeedback { } } - // show the output div - $(this.outDiv).show(); - // Now handle autograding // autograding takes the results of the hidden suffix if exist // otherwise take the result of student input From 2f5e908a20d814a375f8057ba44c962c4ab29c4f Mon Sep 17 00:00:00 2001 From: Zihan Wu Date: Wed, 15 Feb 2023 16:31:45 -0500 Subject: [PATCH 5/7] :alembic: Temporary code: Make execution based feedback consistent with normal execution --- runestone/hparsons/js/SQLFeedback.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/runestone/hparsons/js/SQLFeedback.js b/runestone/hparsons/js/SQLFeedback.js index db1cd1dab..c3a2ddc4b 100644 --- a/runestone/hparsons/js/SQLFeedback.js +++ b/runestone/hparsons/js/SQLFeedback.js @@ -167,9 +167,12 @@ export default class SQLFeedback extends HParsonsFeedback { if (executionSuccessFlag) { this.results = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.input)); // always render full execution results of student input regardless of success/failure - this.visualizeResults(respDiv, this.results); + // for lab study: to be consistent with code execution, only visualize last entry + // this.visualizeResults(respDiv, this.results); if (this.results.at(-1).status == 'failure') { // if error occured in student input, stop executing suffix/unitttest + // and visualize the error + this.visualizeResults(respDiv, this.results); executionSuccessFlag = false; } } @@ -182,6 +185,9 @@ export default class SQLFeedback extends HParsonsFeedback { if (this.suffixresults.at(-1).status == 'failure') { // if error occured in hidden suffix, visualize the results this.visualizeResults(respDiv, this.suffixresults, "Error executing hidden code in suffix"); + } else { + // for study: visualize last result (suffix is always select *) + this.visualizeResults(respDiv, this.suffixresults); } } From 7f7f614e8c84b2eefc62d604e21f675c40a3c527 Mon Sep 17 00:00:00 2001 From: amy21206 Date: Wed, 15 Feb 2023 18:14:00 -0500 Subject: [PATCH 6/7] fixing no results when no suffix --- runestone/hparsons/js/SQLFeedback.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runestone/hparsons/js/SQLFeedback.js b/runestone/hparsons/js/SQLFeedback.js index c3a2ddc4b..4bf3b9e2e 100644 --- a/runestone/hparsons/js/SQLFeedback.js +++ b/runestone/hparsons/js/SQLFeedback.js @@ -174,6 +174,8 @@ export default class SQLFeedback extends HParsonsFeedback { // and visualize the error this.visualizeResults(respDiv, this.results); executionSuccessFlag = false; + } else if (query.suffix != null) { + this.visualizeResults(respDiv, this.results); } } From 0c56e206c558b447164ff1a83358e9c54800060d Mon Sep 17 00:00:00 2001 From: amy21206 Date: Thu, 16 Feb 2023 13:28:30 -0500 Subject: [PATCH 7/7] for study: visualize query result if there's no suffix --- runestone/hparsons/js/SQLFeedback.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runestone/hparsons/js/SQLFeedback.js b/runestone/hparsons/js/SQLFeedback.js index 4bf3b9e2e..7c17efd57 100644 --- a/runestone/hparsons/js/SQLFeedback.js +++ b/runestone/hparsons/js/SQLFeedback.js @@ -174,7 +174,7 @@ export default class SQLFeedback extends HParsonsFeedback { // and visualize the error this.visualizeResults(respDiv, this.results); executionSuccessFlag = false; - } else if (query.suffix != null) { + } else if (!query.suffix) { this.visualizeResults(respDiv, this.results); } }