Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into dev/riknoll/the-gre…
Browse files Browse the repository at this point in the history
…at-blockly-swap
  • Loading branch information
riknoll committed Feb 15, 2024
2 parents 0009614 + 461a8db commit 3f2c12e
Show file tree
Hide file tree
Showing 14 changed files with 235 additions and 75 deletions.
18 changes: 18 additions & 0 deletions common-docs/teachertool/catalog-shared.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@
"use": "variable_accessed",
"template": "At least one variable is accessed",
"docPath": "/teachertool"
},
{
"id": "8E6F9A92-2D22-4BB0-A77A-BD7490D3CEF7",
"use": "variable_set_random",
"template": "A custom variable's value is set to a random number",
"docPath": "/teachertool"
},
{
"id": "E8DAD360-F4AB-4E6B-9981-C14BDEE1295B",
"use": "device_random_used",
"template": "The math random number generator block is used",
"docPath": "/teachertool"
},
{
"id": "DC564060-F177-46CC-A3AC-890CD8545972",
"use": "num_compare_eq",
"template": "Compare two numbers for equality",
"docPath": "/teachertool"
}
]
}
54 changes: 54 additions & 0 deletions common-docs/teachertool/validator-plans-shared.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,60 @@
}
}
]
},
{
".desc": "A custom variable's value is set to a random number",
"name": "variable_set_random",
"threshold": 1,
"checks": [
{
"validator": "blocksExist",
"blockCounts": {
"variables_set": 1
},
"childValidatorPlans": ["device_random_used"]
}
]
},
{
".desc": "Random number block used in project",
"name": "device_random_used",
"threshold": 1,
"checks": [
{
"validator": "blocksExist",
"blockCounts": {
"device_random": 1
}
}
]
},
{
".desc": "make sure two numbers are equal",
"name": "num_compare_eq",
"threshold": 1,
"checks": [
{
"validator": "blockFieldValueExists",
"fieldType": "OP",
"fieldValue": "EQ",
"blockType": "logic_compare",
"childValidatorPlans": ["two_nums_exist"]
}
]
},
{
".desc": "two numbers exist",
"name": "two_nums_exist",
"threshold": 1,
"checks": [
{
"validator": "blocksExist",
"blockCounts": {
"math_number": 2
}
}
]
}
]
}
1 change: 1 addition & 0 deletions localtypings/pxteditor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ declare namespace pxt.editor {
export interface EditorMessageRunEvalRequest extends EditorMessageRequest {
action: "runeval";
validatorPlan: pxt.blocks.ValidatorPlan;
planLib: pxt.blocks.ValidatorPlan[];
}

export interface EditorMessageRenderBlocksResponse {
Expand Down
8 changes: 8 additions & 0 deletions localtypings/validatorPlan.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ declare namespace pxt.blocks {
// Each type of validation will need to implement its own ValidatorCheck based on this.
export interface ValidatorCheckBase {
validator: string;
childValidatorPlans?: string[]
}

// Inputs for "Blocks Exist" validation.
Expand All @@ -38,4 +39,11 @@ declare namespace pxt.blocks {
export interface EvaluationResult {
result: boolean;
}

export interface BlockFieldValueExistsCheck extends ValidatorCheckBase {
validator: "blockFieldValueExists";
fieldType: string;
fieldValue: string;
blockType: string;
}
}
2 changes: 1 addition & 1 deletion pxteditor/code-validation/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from "./runValidatorPlanAsync";
export * from "./runValidatorPlan";
export * from "./validateBlocksExist";
export * from "./validateBlocksInSetExist";
export * from "./validateCommentsExist";
Expand Down
100 changes: 100 additions & 0 deletions pxteditor/code-validation/runValidatorPlan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/// <reference path="../../localtypings/validatorPlan.d.ts" />

import * as Blockly from "blockly";

import { validateBlockFieldValueExists } from "./validateBlockFieldValueExists";
import { validateBlocksExist } from "./validateBlocksExist";
import { validateBlocksInSetExist } from "./validateBlocksInSetExist";
import { validateBlockCommentsExist } from "./validateCommentsExist";
import { validateSpecificBlockCommentsExist } from "./validateSpecificBlockCommentsExist";

export function runValidatorPlan(usedBlocks: Blockly.Block[], plan: pxt.blocks.ValidatorPlan, planLib: pxt.blocks.ValidatorPlan[]): boolean {
const startTime = Date.now();
let checksSucceeded = 0;
let successfulBlocks: Blockly.Block[] = [];

for (const check of plan.checks) {
let checkPassed = false;
switch (check.validator) {
case "blocksExist":
[successfulBlocks, checkPassed] = [...runBlocksExistValidation(usedBlocks, check as pxt.blocks.BlocksExistValidatorCheck)];
break;
case "blockCommentsExist":
checkPassed = runValidateBlockCommentsExist(usedBlocks, check as pxt.blocks.BlockCommentsExistValidatorCheck);
break;
case "specificBlockCommentsExist":
checkPassed = runValidateSpecificBlockCommentsExist(usedBlocks, check as pxt.blocks.SpecificBlockCommentsExistValidatorCheck);
break;
case "blocksInSetExist":
[successfulBlocks, checkPassed] = [...runBlocksInSetExistValidation(usedBlocks, check as pxt.blocks.BlocksInSetExistValidatorCheck)];
break;
case "blockFieldValueExists":
[successfulBlocks, checkPassed] = [...runBlockFieldValueExistsValidation(usedBlocks, check as pxt.blocks.BlockFieldValueExistsCheck)];
break;
default:
pxt.debug(`Unrecognized validator: ${check.validator}`);
checkPassed = false;
break;
}

if (checkPassed && check.childValidatorPlans) {
for (const planName of check.childValidatorPlans) {
let timesPassed = 0;
for (const parentBlock of successfulBlocks) {
const blocksToUse = parentBlock.getChildren(true);
const childPlan = planLib.find((plan) => plan.name === planName);
const childPassed = runValidatorPlan(blocksToUse, childPlan, planLib);
timesPassed += childPassed ? 1 : 0;
}
checkPassed = checkPassed && timesPassed > 0;
}
}
checksSucceeded += checkPassed ? 1 : 0;
}

const passed = checksSucceeded >= plan.threshold;

pxt.tickEvent("validation.evaluation_complete", {
plan: plan.name,
durationMs: Date.now() - startTime,
passed: `${passed}`,
});

return passed;
}

function runBlocksExistValidation(usedBlocks: Blockly.Block[], inputs: pxt.blocks.BlocksExistValidatorCheck): [Blockly.Block[], boolean] {
const blockResults = validateBlocksExist({ usedBlocks, requiredBlockCounts: inputs.blockCounts });
let successfulBlocks: Blockly.Block[] = [];
if (blockResults.passed) {
const blockIdsFromValidator = Object.keys(inputs.blockCounts)
const blockId = blockIdsFromValidator?.[0];
successfulBlocks = blockResults.successfulBlocks.length ? blockResults.successfulBlocks[0][blockId] : [];
}
return [successfulBlocks, blockResults.passed];
}

function runValidateBlockCommentsExist(usedBlocks: Blockly.Block[], inputs: pxt.blocks.BlockCommentsExistValidatorCheck): boolean {
const blockResults = validateBlockCommentsExist({ usedBlocks, numRequired: inputs.count });
return blockResults.passed;
}

function runValidateSpecificBlockCommentsExist(usedBlocks: Blockly.Block[], inputs: pxt.blocks.SpecificBlockCommentsExistValidatorCheck): boolean {
const blockResults = validateSpecificBlockCommentsExist({ usedBlocks, blockType: inputs.blockType });
return blockResults.passed;
}

function runBlocksInSetExistValidation(usedBlocks: Blockly.Block[], inputs: pxt.blocks.BlocksInSetExistValidatorCheck): [Blockly.Block[], boolean] {
const blockResults = validateBlocksInSetExist({ usedBlocks, blockIdsToCheck: inputs.blocks, count: inputs.count });
return [blockResults.successfulBlocks, blockResults.passed];
}

function runBlockFieldValueExistsValidation(usedBlocks: Blockly.Block[], inputs: pxt.blocks.BlockFieldValueExistsCheck): [Blockly.Block[], boolean] {
const blockResults = validateBlockFieldValueExists({
usedBlocks,
fieldType: inputs.fieldType,
fieldValue: inputs.fieldValue,
specifiedBlock: inputs.blockType
});
return [blockResults.successfulBlocks, blockResults.passed];
}
69 changes: 0 additions & 69 deletions pxteditor/code-validation/runValidatorPlanAsync.ts

This file was deleted.

21 changes: 21 additions & 0 deletions pxteditor/code-validation/validateBlockFieldValueExists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// validates that one or more blocks comments are in the project
// returns the blocks that have comments for teacher tool scenario
export function validateBlockFieldValueExists({ usedBlocks, fieldType, fieldValue, specifiedBlock }: {
usedBlocks: Blockly.Block[],
fieldType: string,
fieldValue: string,
specifiedBlock: string,
}): {
successfulBlocks: Blockly.Block[],
passed: boolean
} {
const enabledSpecifiedBlocks = usedBlocks.filter((block) =>
block.isEnabled() && block.type === specifiedBlock
);

const successfulBlocks = enabledSpecifiedBlocks.filter((block) =>
block.getFieldValue(fieldType) === fieldValue
);

return { successfulBlocks, passed: successfulBlocks.length > 0 };
}
15 changes: 15 additions & 0 deletions pxteditor/code-validation/validateBlocksExist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,25 @@ export function validateBlocksExist({ usedBlocks, requiredBlockCounts }: {
missingBlocks: string[],
disabledBlocks: string[],
insufficientBlocks: string[],
successfulBlocks: pxt.Map<Blockly.Block[]>[],
passed: boolean
} {
let missingBlocks: string[] = [];
let disabledBlocks: string[] = [];
let insufficientBlocks: string[] = [];
let successfulBlocks: pxt.Map<Blockly.Block[]>[] = [];
const userBlocksEnabledByType = usedBlocks?.reduce((acc: pxt.Map<number>, block) => {
acc[block.type] = (acc[block.type] || 0) + (block.isEnabled() ? 1 : 0);
return acc;
}, {});

for (const [requiredBlockId, requiredCount] of Object.entries(requiredBlockCounts || {})) {
const countForBlock = userBlocksEnabledByType[requiredBlockId];
const passedBlocks = usedBlocks.filter((block) => block.type === requiredBlockId);
if (passedBlocks.length > 0) {
successfulBlocks.push({ [requiredBlockId]: passedBlocks})
}

if (countForBlock === undefined) {
// user did not use a specific block
missingBlocks.push(requiredBlockId);
Expand All @@ -30,9 +38,16 @@ export function validateBlocksExist({ usedBlocks, requiredBlockCounts }: {
}
}

const passed =
missingBlocks.length === 0 &&
disabledBlocks.length === 0 &&
insufficientBlocks.length === 0;

return {
missingBlocks,
disabledBlocks,
insufficientBlocks,
successfulBlocks,
passed
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export function validateSpecificBlockCommentsExist({ usedBlocks, blockType }: {
} {
const allSpecifcBlocks = usedBlocks.filter((block) => block.type === blockType);
const uncommentedBlocks = allSpecifcBlocks.filter((block) => !block.getCommentText());
return { uncommentedBlocks, passed: uncommentedBlocks.length === 0 };
const passed = allSpecifcBlocks.length === 0 ? false : uncommentedBlocks.length === 0;
return { uncommentedBlocks, passed };
}
5 changes: 3 additions & 2 deletions pxteditor/editorcontroller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference path="../localtypings/pxteditor.d.ts" />

import { runValidatorPlanAsync } from "./code-validation/runValidatorPlanAsync";
import { runValidatorPlan } from "./code-validation/runValidatorPlan";
import IProjectView = pxt.editor.IProjectView;

const pendingRequests: pxt.Map<{
Expand Down Expand Up @@ -157,10 +157,11 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)
case "runeval": {
const evalmsg = data as pxt.editor.EditorMessageRunEvalRequest;
const plan = evalmsg.validatorPlan;
const planLib = evalmsg.planLib;
return Promise.resolve()
.then(() => {
const blocks = projectView.getBlocks();
return runValidatorPlanAsync(blocks, plan)})
return runValidatorPlan(blocks, plan, planLib)})
.then (results => {
resp = { result: results };
});
Expand Down
Loading

0 comments on commit 3f2c12e

Please sign in to comment.