-
Notifications
You must be signed in to change notification settings - Fork 593
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Load Teacher Tools Catalog from Docs, Store in State #9829
Conversation
…ag for logging errors.
…riteria at once, and utilize modal actions instead of creating separate buttons in the modal body.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have lots of questions and comments, but this is super exciting stuff. Great work!!
pxtblocks/code-validation/types.ts
Outdated
} | ||
|
||
// Possible values for CriteriaParameterPicker, these are different ways a user may enter parameter values. | ||
export type CriteriaParameterPicker = "blocksPicker" | "numericInput"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Styling nit but also a question. I was thinking that it would be nice to have types defined before the interfaces, but maybe it makes more sense to have this close to CriteriaParameter
since that's where it's used. Are there standards around this area?
teachertool/package-lock.json
Outdated
@@ -9,11 +9,13 @@ | |||
"version": "0.1.0", | |||
"dependencies": { | |||
"@types/node": "^16.11.33", | |||
"@types/uuid": "^9.0.7", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you look into if we have this somewhere common? I'm wondering if there is somewhere client-side where we make unique ids already and we can thus avoid adding this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked and wasn't able to find one. I was also surprised that we hadn't already needed to do this elsewhere, but searching for guid/uuid didn't turn up much at all.
|
||
const CatalogModal: React.FC<IProps> = ({}) => { | ||
const { state: teacherTool, dispatch } = useContext(AppStateContext); | ||
const [ checkedCriteriaIds, setCheckedCriteria ] = useState<string[]>([]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: is the <string[]>
needed when using useState
? I don't think I usually include it when the types are more definitive (list, string, number, bool) and want to make sure that I'm following convention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to make this into a set? I think we could make handleCheckboxChange
simpler if so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I add it when the type is not easily inferred from the default value. So it's not really needed for a number or a string, but in this case it's not clear what type is expected in the list, so I think it's helpful to include.
Set might work well. I'll take a look.
hideCatalogModal(); | ||
|
||
// Clear for next open. | ||
setCheckedCriteria([]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this something we want to do? I thought we wanted to keep criteria that have already been added to the rubric "checked" so we don't have duplicates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By design, we want to enable duplicates of some criteria. We should probably hide criteria from this list if they've already been added and duplicates aren't allowed (I forgot to do that, but I can add it...). The checks are specifically for which new criteria we're adding in this "modal-session" (for lack of a better term).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense! I don't think the "don't allow duplicates" case is something that needs to be included in this PR.
export const logError = (name: string, details: string) => { | ||
pxt.tickEvent("teachertool.error", { name: formatName(name), message: details }); | ||
console.error(formatMessageForConsole(`${name}: ${details}`)); | ||
export const logError = (name: string, details: string, props: pxt.Map<string | number> = {}) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: rename props
to data
or something similar. When I think of props, I think of React props..
const { state: teacherTool, dispatch } = useContext(AppStateContext); | ||
const [ checkedCriteriaIds, setCheckedCriteria ] = useState<string[]>([]); | ||
|
||
function handleCheckboxChange(criteria: pxt.blocks.CatalogCriteria, newValue: boolean) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something I was thinking about.. What if we added a selected
field to the Catalog Criteria Schema? Since we parse the catalog from docs into state, we can manage whether or not something is checked from the catalog directly. The look-up for an object like that would be faster, right? And then when the checkbox is changed, we can just toggle that field. The done and close modal would have to be modified, though. Also, I'm not a huge fan of the implications that this has on state.. it feels kinda icky that a modal would have so much power over the state of the catalog, but I also feel like this handler should be simpler than having to do a look-up or filter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want the user to be able to have multiple instances of the same catalog criteria, so just having selected
on catalog criteria doesn't quite work (if it did, there'd be no real need for criteria instances at all).
I'm not sure I quite understand the concern around the handler. Could you elaborate on that a bit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, my idea would be that the selected
state on the catalog would be reset every time the modal is closed so when the user re-opens the modal, they have a fresh state. This might be more work than it's worth.
In regards to the handler, I'm a bit worried about a list operation every time the box is checked or unchecked. To be fair, they shouldn't be very computationally taxing, but the list operations plus conditions when we want to toggle a condition for something seems like a lot of logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per our discussion in person, I think something like that might work (we'd need a new component like a CriteriaCheckbox to store selected
, since I don't think we want UI-related fields to leak out into non-UI-specific classes), but it would probably just move the complexity elsewhere rather than actually simplifying things, so I'm inclined to leave this as-is at the moment.
const { state: teacherTool, dispatch } = stateAndDispatch(); | ||
|
||
// Create instances for each of the catalog criteria. | ||
const instances = catalogCriteriaIds.reduce((accumulator, catalogCriteriaId) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still reviewing, but wanted to send out my comments so far.
@@ -37,9 +45,11 @@ function App() { | |||
<HeaderBar /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a note that we need to put all this rendering behind the ready
flag. Otherwise stateAndDispatch
may not be ready in code triggered by the first UI render.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave this be since you've added one in #9830
<Checkbox | ||
id={`chk${criteria.id}`} | ||
className="catalog-item" | ||
label={criteria.template} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: Figure out how to send this through localization.
criteria: pxt.blocks.CatalogCriteria[]; | ||
} | ||
|
||
export async function loadCatalog() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export async function loadCatalog() { | |
export async function loadCatalogAsync() { |
Update filename to match.
import * as Actions from "../state/actions"; | ||
import { logDebug, logError } from "../services/loggingService"; | ||
|
||
export async function addCriteriaToRubric(catalogCriteriaIds: string[]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export async function addCriteriaToRubric(catalogCriteriaIds: string[]) { | |
export async function addCriteriaToRubricAsync(catalogCriteriaIds: string[]) { |
import { stateAndDispatch } from "../state"; | ||
import * as Actions from "../state/actions"; | ||
|
||
export async function hideCatalogModal() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be async?
import * as Actions from "../state/actions"; | ||
import { logDebug } from "../services/loggingService"; | ||
|
||
export async function removeCriteriaFromRubric(instance: pxt.blocks.CriteriaInstance) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be async?
teachertool/src/utils/index.ts
Outdated
@@ -1,5 +1,6 @@ | |||
import { nanoid } from "nanoid"; | |||
import { NotificationWithId } from "../types"; | |||
import { stateAndDispatch } from "../state"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep app state out of the utils file. I suggest moving getCatalogCriteriaWithId
to /src/state/helpers.ts. We'll probably accumulate more of these helpers, and I'd like to keep utils uncomplicated.
const catalogInfo = catalogInfoParsed as CatalogInfo; | ||
fullCatalog = fullCatalog.concat(catalogInfo.criteria ?? []); | ||
} catch (e) { | ||
logError("parse_catalog_failed", e as string, {catalogFile}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be nice to organize these error codes in one place and refer to them here like Errors.parse_catalog_failed
. This would simplify looking up error codes when building a Kusto query.
pxtblocks/code-validation/types.ts
Outdated
} | ||
|
||
// Possible values for CriteriaParameterPicker, these are different ways a user may enter parameter values. | ||
export type CriteriaParameterPicker = "blocksPicker" | "numericInput"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"numericInput"
I don't think we need a picker for numbers. We can make the param value directly editable.
pxtblocks/code-validation/types.ts
Outdated
export interface CriteriaParameter { | ||
name: string; | ||
type: string; | ||
picker: CriteriaParameterPicker; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I'm thinking we don't need a picker
field at all. We can use the type
field to infer the picker. type: "block_id"
for the block picker.
|
||
export async function loadCatalog() { | ||
const { dispatch } = stateAndDispatch(); | ||
const catalogFiles = pxt.options.debug ? prodFiles.concat(testFiles) : prodFiles; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
teachertool/src/App.tsx
Outdated
NotificationService.initialize(); | ||
|
||
// Load criteria catalog | ||
loadCatalog(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate PR :)
let catalogContent = ""; | ||
try { | ||
const catalogResponse = await fetch(catalogFile); | ||
catalogContent = await catalogResponse.text(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}, | ||
] | ||
|
||
return teacherTool.modal ? ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
return teacherTool.modal ? ( | ||
<Modal className="catalog-modal" title={lf("Select the criteria you'd like to include")} onClose={closeModal} actions={modalActions}> | ||
<div className="catalog-container" title={lf("Select the criteria you'd like to include")}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[](http://example.com/codeflow?start=12&length=91)
redundant now?
pxtblocks/code-validation/types.ts
Outdated
@@ -0,0 +1,35 @@ | |||
namespace pxt.blocks { | |||
// A criteria defined in the catalog of all possible criteria for the user to choose from when creating a rubric. | |||
export interface CatalogCriteria { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
teachertool/src/state/reducer.ts
Outdated
catalog: action.catalog, | ||
}; | ||
} | ||
case "ADD_CRITERIA_INSTANCES": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const catalogCriteria = getCatalogCriteriaWithId(criteriaInstance.catalogCriteriaId); | ||
|
||
return criteriaInstance?.catalogCriteriaId && ( | ||
<div className="criteria-instance-display" id={`criteriaInstance${criteriaInstance.instanceId}`}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { stateAndDispatch } from "../state"; | ||
import * as Actions from "../state/actions"; | ||
|
||
export async function hideCatalogModal() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…dempotent "set" action (apparently required by react).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the walkthrough you did yesterday, I have no concerns about this PR. LGTM! :)
… on the existing debug flag which could have unintended consequences
…ks/add_catalog
@@ -28,6 +34,10 @@ function App() { | |||
const cfg = await downloadTargetConfigAsync(); | |||
dispatch(Actions.setTargetConfig(cfg || {})); | |||
pxt.BrowserUtils.initTheme(); | |||
|
|||
// Load criteria catalog | |||
await loadCatalogAsync(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eanders-ms I wasn't totally sure how to plug into this initialization code. Is this suitable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, looks good! If we end up with a lot of async tasks here we should try to parallelize some of them. Nothing to worry about yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
@@ -0,0 +1,8 @@ | |||
export enum ErrorCode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh nice. We might want to strongly type logError to this. logError(errorCode: ErrorCode,
This change includes the following:
Notably, it does not include:
A few notes:
Unfortunately upload targets don't work well with docs, but here are some screenshots:
