From fe3f3b4c6b759f0337de228cc72280acc1950a1d Mon Sep 17 00:00:00 2001 From: Kim Ying <15070078+kimprice@users.noreply.github.com> Date: Thu, 11 Jan 2024 16:41:39 -0800 Subject: [PATCH] Teacher tool: Initial validation logic (#9823) * set up evaluation results * whitespace fixes Co-authored-by: Eric Anderson * whitespace fixes Co-authored-by: Thomas Sparks <69657545+thsparks@users.noreply.github.com> * throw error for non-blocks project instead of converting to blocks --------- Co-authored-by: Eric Anderson Co-authored-by: Thomas Sparks <69657545+thsparks@users.noreply.github.com> --- pxtblocks/code-validation/evaluationResult.ts | 2 +- pxtblocks/code-validation/rubricCriteria.ts | 49 +++++++++++++++++++ pxteditor/editor.ts | 1 + pxteditor/editorcontroller.ts | 6 +-- teachertool/src/App.tsx | 8 +-- .../src/components/EvalResultDisplay.tsx | 11 ++++- teachertool/src/teacherTool.css | 23 +++++++++ webapp/src/app.tsx | 9 ++++ 8 files changed, 100 insertions(+), 9 deletions(-) diff --git a/pxtblocks/code-validation/evaluationResult.ts b/pxtblocks/code-validation/evaluationResult.ts index 982d4bc62ab8..00e438ed6e53 100644 --- a/pxtblocks/code-validation/evaluationResult.ts +++ b/pxtblocks/code-validation/evaluationResult.ts @@ -1,5 +1,5 @@ namespace pxt.blocks { export interface EvaluationResult { - passed: boolean; + blockIdResults: pxt.Map; } } diff --git a/pxtblocks/code-validation/rubricCriteria.ts b/pxtblocks/code-validation/rubricCriteria.ts index 7459794b7017..9e3b335d0e3d 100644 --- a/pxtblocks/code-validation/rubricCriteria.ts +++ b/pxtblocks/code-validation/rubricCriteria.ts @@ -23,6 +23,55 @@ namespace pxt.blocks { } } + function blockSetToRequiredBlockCounts(blockSet: BlockSet): pxt.Map { + const requiredBlockCounts: pxt.Map = {}; + blockSet.blocks.forEach((block) => { + requiredBlockCounts[block] = blockSet.count; + }); + return requiredBlockCounts; + } + + export function validateProject(usedBlocks: Blockly.Block[], rubric: string): EvaluationResult { + const rubricData = parseRubric(rubric); + const finalResult: pxt.Map = {}; + rubricData.criteria.forEach((criteria: RubricCriteria) => { + (criteria as BlockCheckCriteria).blockRequirements.forEach((blockSet) => { + const result = validateBlockSet(usedBlocks, blockSet); + Object.keys(result).forEach((blockId) => { + finalResult[blockId] = result[blockId]; + }); + }); + }); + return { blockIdResults: finalResult } as EvaluationResult; + } + + + function validateBlockSet(usedBlocks: Blockly.Block[], blockSet: BlockSet): pxt.Map { + const requiredBlockCounts = blockSetToRequiredBlockCounts(blockSet); + const blockResults: pxt.Map = {}; + Object.keys(requiredBlockCounts).forEach((blockId) => { + blockResults[blockId] = true; + }); + const { + missingBlocks, + disabledBlocks, + insufficientBlocks + } = pxt.blocks.validateBlocksExist({ + usedBlocks: usedBlocks, + requiredBlockCounts: requiredBlockCounts, + }); + missingBlocks.forEach((blockId) => { + blockResults[blockId] = false; + }); + disabledBlocks.forEach((blockId) => { + blockResults[blockId] = false; + }); + insufficientBlocks.forEach((blockId) => { + blockResults[blockId] = false; + }); + return blockResults; + } + export abstract class RubricCriteria { displayText: string; abstract criteriaId: string; diff --git a/pxteditor/editor.ts b/pxteditor/editor.ts index 11c5b373d426..07ca98a9c95d 100644 --- a/pxteditor/editor.ts +++ b/pxteditor/editor.ts @@ -335,6 +335,7 @@ namespace pxt.editor { blocksScreenshotAsync(pixelDensity?: number, encodeBlocks?: boolean): Promise; renderBlocksAsync(req: EditorMessageRenderBlocksRequest): Promise; renderPythonAsync(req: EditorMessageRenderPythonRequest): Promise; + getBlocks(): Blockly.Block[]; toggleHighContrast(): void; setHighContrast(on: boolean): void; diff --git a/pxteditor/editorcontroller.ts b/pxteditor/editorcontroller.ts index 55e64e32576e..98da38ca339e 100644 --- a/pxteditor/editorcontroller.ts +++ b/pxteditor/editorcontroller.ts @@ -548,9 +548,9 @@ namespace pxt.editor { const evalmsg = data as EditorMessageRunEvalRequest; const rubric = evalmsg.rubric; return Promise.resolve() - .then(() => ( - // TODO : call into evaluation function. - { passed: true } as pxt.blocks.EvaluationResult)) + .then(() => { + const blocks = projectView.getBlocks(); + return pxt.blocks.validateProject(blocks, rubric)}) .then (results => { resp = results; }); diff --git a/teachertool/src/App.tsx b/teachertool/src/App.tsx index 8903bfd32626..2635ba2cc701 100644 --- a/teachertool/src/App.tsx +++ b/teachertool/src/App.tsx @@ -35,9 +35,11 @@ function App() { return (
- - - +
+ + + +
); diff --git a/teachertool/src/components/EvalResultDisplay.tsx b/teachertool/src/components/EvalResultDisplay.tsx index 6e7ac7162c6e..fad4eef52d18 100644 --- a/teachertool/src/components/EvalResultDisplay.tsx +++ b/teachertool/src/components/EvalResultDisplay.tsx @@ -14,8 +14,15 @@ const EvalResultDisplay: React.FC = ({}) => {

{lf("Project: {0}", teacherTool.projectMetadata.name)}

{teacherTool.currentEvalResult === undefined &&
} - {teacherTool.currentEvalResult?.passed === true &&

Passed!

} - {teacherTool.currentEvalResult?.passed === false &&

Failed!

} + {Object.keys(teacherTool.currentEvalResult?.blockIdResults ?? {}).map((id) => { + const result = teacherTool.currentEvalResult?.blockIdResults[id]; + return ( +
+

{id}:

+

{result ? "passed" : "failed"}

+
+ ); + })}
)} diff --git a/teachertool/src/teacherTool.css b/teachertool/src/teacherTool.css index 7d4e6fff27c1..e6adffeec87f 100644 --- a/teachertool/src/teacherTool.css +++ b/teachertool/src/teacherTool.css @@ -76,6 +76,13 @@ code { overflow: auto; } +.inner-app-container { + display: flex; + flex-direction: column; + min-height: 100%; + width: 100%; +} + .noclick { pointer-events: none; cursor: default; @@ -397,6 +404,22 @@ code { border-bottom: 1px solid #ddd; margin: 1rem 0; padding: 1rem; + overflow: auto; +} + +.result-block-id { + display: flex; + padding-left: .5rem; + margin: .2rem 0; +} + +.result-block-id p { + margin: 0; +} + +.block-id-label { + font-weight: 700; + padding-right: .5rem; } .positive-text { diff --git a/webapp/src/app.tsx b/webapp/src/app.tsx index abbb5bc048d3..94bf1cb9f79e 100644 --- a/webapp/src/app.tsx +++ b/webapp/src/app.tsx @@ -4038,6 +4038,15 @@ export class ProjectView }); } + getBlocks(): Blockly.Block[] { + if (!this.isBlocksActive()) { + console.error("Trying to get blocks from a non-blocks editor."); + throw new Error("Trying to get blocks from a non-blocks editor."); + } + + return this.blocksEditor.editor.getAllBlocks(false); + } + launchFullEditor() { Util.assert(pxt.shell.isSandboxMode());