Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/microsoft/pxt into thspar…
Browse files Browse the repository at this point in the history
…ks/teacher_tool/import_export_rubric
  • Loading branch information
thsparks committed Feb 1, 2024
2 parents 947f14c + 1b92ccc commit df9f72a
Show file tree
Hide file tree
Showing 37 changed files with 584 additions and 244 deletions.
30 changes: 0 additions & 30 deletions pxtblocks/code-validation/rubric.ts

This file was deleted.

90 changes: 0 additions & 90 deletions pxtblocks/code-validation/rubricCriteria.ts

This file was deleted.

10 changes: 7 additions & 3 deletions pxtblocks/code-validation/runValidatorPlanAsync.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
namespace pxt.blocks {

const maxConcurrentChecks = 4;

export async function runValidatorPlanAsync(usedBlocks: Blockly.Block[], plan: ValidatorPlan): Promise<boolean> {
// Each plan can have multiple checks it needs to run.
// Run all of them in parallel, and then check if the number of successes is greater than the specified threshold.
// TBD if it's faster to run in parallel without short-circuiting once the threshold is reached, or if it's faster to run sequentially and short-circuit.
const startTime = Date.now();

const checkRuns = pxt.Util.promisePoolAsync(4, plan.checks, async (check: ValidatorCheckBase): Promise<boolean> => {
const checkRuns = pxt.Util.promisePoolAsync(maxConcurrentChecks, plan.checks, async (check: ValidatorCheckBase): Promise<boolean> => {
switch (check.validator) {
case "blocksExist":
return runBlocksExistValidation(usedBlocks, check as BlocksExistValidatorCheck);
Expand All @@ -18,14 +21,15 @@ namespace pxt.blocks {

const results = await checkRuns;
const successCount = results.filter((r) => r).length;
const passed = successCount >= plan.threshold;

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

return successCount >= plan.threshold;
return passed;
}

function runBlocksExistValidation(usedBlocks: Blockly.Block[], inputs: BlocksExistValidatorCheck): boolean {
Expand Down
3 changes: 3 additions & 0 deletions teachertool/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { CatalogModal } from "./components/CatalogModal";
import { postNotification } from "./transforms/postNotification";
import { loadCatalogAsync } from "./transforms/loadCatalogAsync";
import { loadValidatorPlansAsync } from "./transforms/loadValidatorPlansAsync";
import { tryLoadLastActiveRubricAsync } from "./transforms/tryLoadLastActiveRubricAsync";

export const App = () => {
const { state, dispatch } = useContext(AppStateContext);
Expand All @@ -34,6 +35,8 @@ export const App = () => {
await loadCatalogAsync();
await loadValidatorPlansAsync();

await tryLoadLastActiveRubricAsync();

// TODO: Remove this. Delay app init to expose any startup race conditions.
setTimeout(() => {
// Test notification
Expand Down
17 changes: 13 additions & 4 deletions teachertool/src/components/ActiveRubricDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
/// <reference path="../../../built/pxtblocks.d.ts"/>

import { useContext } from "react";
import { useContext, useState } from "react";
import { AppStateContext } from "../state/appStateContext";
import { getCatalogCriteriaWithId } from "../state/helpers";
import { Button } from "react-common/components/controls/Button";
import { removeCriteriaFromRubric } from "../transforms/removeCriteriaFromRubric";
import { showCatalogModal } from "../transforms/showCatalogModal";
import { setRubricName } from "../transforms/setRubricName";
import { DebouncedInput } from "./DebouncedInput";

interface IProps {}

export const ActiveRubricDisplay: React.FC<IProps> = ({}) => {
const { state: teacherTool, dispatch } = useContext(AppStateContext);
const { state: teacherTool } = useContext(AppStateContext);

return (
<div className="rubric-display">
<h3>{lf("Rubric")}</h3>
{teacherTool.selectedCriteria?.map(criteriaInstance => {
<DebouncedInput
label={lf("Rubric Name")}
ariaLabel={lf("Rubric Name")}
onChange={setRubricName}
placeholder={lf("Rubric Name")}
initialValue={teacherTool.rubric.name}
preserveValueOnBlur={true}
/>
{teacherTool.rubric.criteria?.map(criteriaInstance => {
if (!criteriaInstance) return null;

const catalogCriteria = getCatalogCriteriaWithId(criteriaInstance.catalogCriteriaId);
Expand Down
43 changes: 43 additions & 0 deletions teachertool/src/components/DebouncedInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useEffect, useRef } from "react";
import { Input, InputProps } from "react-common/components/controls/Input";

export interface DebouncedInputProps extends InputProps {
intervalMs?: number; // Default 500 ms
}

// This functions like the React Common Input, but debounces onChange calls,
// so if onChange is called multiple times in quick succession, it will only
// be executed once after a pause of the specified `interval` in milliseconds.
export const DebouncedInput: React.FC<DebouncedInputProps> = ({ intervalMs = 500, ...props }) => {
const timerId = useRef<NodeJS.Timeout | undefined>(undefined);
const latestValue = useRef<string>("");

const sendChange = () => {
if (props.onChange) {
props.onChange(latestValue.current);
}
};

// If the timer is pending and the component unmounts,
// clear the timer and fire the onChange event immediately.
useEffect(() => {
return () => {
if (timerId.current) {
clearTimeout(timerId.current);
sendChange();
}
};
}, []);

const onChangeDebounce = (newValue: string) => {
latestValue.current = newValue;

if (timerId.current) {
clearTimeout(timerId.current);
}

timerId.current = setTimeout(sendChange, intervalMs);
};

return <Input {...props} onChange={onChangeDebounce} />;
};
15 changes: 1 addition & 14 deletions teachertool/src/components/DebugInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,12 @@ import { runEvaluateAsync } from "../transforms/runEvaluateAsync";
interface IProps {}

export const DebugInput: React.FC<IProps> = ({}) => {
const [shareLink, setShareLink] = useState("https://makecode.microbit.org/S95591-52406-50965-65671");

const evaluate = async () => {
await loadProjectMetadataAsync(shareLink);
await runEvaluateAsync();
};

return (
<div className="debug-container">
<div className="single-share-link-input-container">
{lf("Share Link:")}
<Input
id="shareLinkInput"
className="link-input"
placeholder={lf("Share link to validate")}
initialValue={shareLink}
onChange={setShareLink}
/>
</div>
<Button
id="evaluateSingleProjectButton"
className="btn-primary"
Expand All @@ -38,4 +25,4 @@ export const DebugInput: React.FC<IProps> = ({}) => {
/>
</div>
);
};
};
34 changes: 19 additions & 15 deletions teachertool/src/components/EvalResultDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const EvalResultDisplay: React.FC<IProps> = ({}) => {
const { state: teacherTool } = useContext(AppStateContext);

function getTemplateStringFromCriteriaInstanceId(instanceId: string): string {
const catalogCriteriaId = teacherTool.selectedCriteria?.find(
const catalogCriteriaId = teacherTool.rubric.criteria?.find(
criteria => criteria.instanceId === instanceId
)?.catalogCriteriaId;
if (!catalogCriteriaId) return "";
Expand All @@ -28,20 +28,24 @@ export const EvalResultDisplay: React.FC<IProps> = ({}) => {
{Object.keys(teacherTool.evalResults ?? {}).map(criteriaInstanceId => {
const result = teacherTool.evalResults[criteriaInstanceId];
const label = getTemplateStringFromCriteriaInstanceId(criteriaInstanceId);
return label && (
<div className="result-block-id" key={criteriaInstanceId}>
<p className="block-id-label">
{getTemplateStringFromCriteriaInstanceId(criteriaInstanceId)}:
</p>
{result === CriteriaEvaluationResult.InProgress && <div className="common-spinner" />}
{result === CriteriaEvaluationResult.CompleteWithNoResult && <p>{lf("N/A")}</p>}
{result === CriteriaEvaluationResult.Fail && (
<p className="negative-text">{lf("Needs Work")}</p>
)}
{result === CriteriaEvaluationResult.Pass && (
<p className="positive-text">{lf("Looks Good!")}</p>
)}
</div>
return (
label && (
<div className="result-block-id" key={criteriaInstanceId}>
<p className="block-id-label">
{getTemplateStringFromCriteriaInstanceId(criteriaInstanceId)}:
</p>
{result === CriteriaEvaluationResult.InProgress && (
<div className="common-spinner" />
)}
{result === CriteriaEvaluationResult.CompleteWithNoResult && <p>{lf("N/A")}</p>}
{result === CriteriaEvaluationResult.Fail && (
<p className="negative-text">{lf("Needs Work")}</p>
)}
{result === CriteriaEvaluationResult.Pass && (
<p className="positive-text">{lf("Looks Good!")}</p>
)}
</div>
)
);
})}
</div>
Expand Down
2 changes: 1 addition & 1 deletion teachertool/src/components/HeaderBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const HeaderBar: React.FC<HeaderBarProps> = () => {
};

return (
<MenuBar className={css["header"]} ariaLabel={lf("Header")}>
<MenuBar className={css["header"]} ariaLabel={lf("Header")} role="navigation">
<div className={css["left-menu"]}>
{getOrganizationLogo()}
{getTargetLogo()}
Expand Down
11 changes: 4 additions & 7 deletions teachertool/src/components/MainPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import * as React from "react";
// eslint-disable-next-line import/no-internal-modules
import css from "./styling/MainPanel.module.scss";

import { DebugInput } from "./DebugInput";
import { MakeCodeFrame } from "./MakecodeFrame";
import { EvalResultDisplay } from "./EvalResultDisplay";
import { ActiveRubricDisplay } from "./ActiveRubricDisplay";
import { SplitPane } from "./SplitPane";
import { RubricWorkspace } from "./RubricWorkspace";
import { ProjectWorkspace } from "./ProjectWorkspace";

interface IProps {}

Expand All @@ -16,13 +15,11 @@ export const MainPanel: React.FC<IProps> = () => {
<SplitPane split={"vertical"} defaultSize={"80%"} primary={"left"}>
{/* Left side */}
<>
<DebugInput />
<ActiveRubricDisplay />
<EvalResultDisplay />
<RubricWorkspace />
</>
{/* Right side */}
<>
<MakeCodeFrame />
<ProjectWorkspace />
</>
</SplitPane>
</div>
Expand Down
Loading

0 comments on commit df9f72a

Please sign in to comment.