-
Notifications
You must be signed in to change notification settings - Fork 350
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
SSS: Improve types for validation #2002
Merged
jeremywiebe
merged 12 commits into
feature/client-validation
from
jer/client-validation-4
Dec 20, 2024
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
8e5e778
Improve types for validation
jeremywiebe 5bc363b
Changeset
jeremywiebe c039d47
Revert @deprecated change to just deprecated
jeremywiebe 606a9b7
REmove unused eslint suppression
jeremywiebe e5c8bc6
Fix comment
jeremywiebe da93805
Specify return type - for some reason TypeScript needs this
jeremywiebe 7ce25ce
Fixing more names/usages of Rubric to be ScoringData
jeremywiebe 2f1fb62
Update packages/perseus/src/validation.types.ts
jeremywiebe a3eed89
Migrate comments to /** */ type JSDoc comments
jeremywiebe cf7d5c1
Rework type tests - clearer?
jeremywiebe 992d461
Add example/more docs to MakeWidgetMap<>`
jeremywiebe 6c65518
Final tweak of comment
jeremywiebe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@khanacademy/perseus": minor | ||
--- | ||
|
||
Add and improve types for scoring and validation |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/** | ||
* This file contains TypeScript type "tests" which ensure that types needed | ||
* for scoring and validation stay in sync with other types in the system. | ||
* | ||
* If you make a change and `Extends<>` starts to complain, that will usually | ||
* mean you've made a change that will cause runtime breakages in scoring or | ||
* validation. ie. The types that should be compatible are no longer | ||
* compatible. Read the TypeScript error message closely and it should point | ||
* you in the right direction. | ||
*/ | ||
import type {PerseusRenderer} from "../perseus-types"; | ||
import type {ScoringDataMap, ValidationDataMap} from "../validation.types"; | ||
|
||
/** | ||
* An utility type that verifies that the given type `E` extends the type `T`. | ||
* This is useful for asserting that one type remains a compatible subset of | ||
* the other. | ||
*/ | ||
type Extends<T, E extends T> = (T) => E; | ||
|
||
// We can use a 'widgets' map from a PerseusRenderer as a ValidationDataMap | ||
type _ = Extends<ValidationDataMap, PerseusRenderer["widgets"]>; | ||
|
||
// We can use a ScoringDataMap as a ValidationDataMap | ||
type __ = Extends<ValidationDataMap, ScoringDataMap>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,19 +5,20 @@ | |
* | ||
* These types are: | ||
* | ||
* `Perseus<Widget>UserInput`: the data returned by the widget that the user | ||
* entered. This is referred to as the 'guess' in some older parts of Perseus. | ||
* * `Perseus<Widget>UserInput`: the data from the widget that represents the | ||
* data the user entered. This is referred to as the 'guess' in some older | ||
* parts of Perseus. | ||
* | ||
* `Perseus<Widget>ValidationData`: the data needed to do validation of the | ||
* user input. Validation refers to the different checks that we can do both on | ||
* the client-side (before submitting user input for scoring) and on the | ||
* server-side (when we score it). As such, it cannot contain any of the | ||
* sensitive scoring data that would reveal the answer. | ||
* * `Perseus<Widget>ValidationData`: the data needed to do validation of the | ||
* user input. Validation refers to the different checks that we can do | ||
* both on the client-side (before submitting user input for scoring) and | ||
* on the server-side (when we score it). As such, it cannot contain any of | ||
* the sensitive scoring data that would reveal the answer. | ||
* | ||
* `Perseus<Widget>ScoringData` (nee `Perseus<Widget>Rubric`): the data needed | ||
* to score the user input. By convention, this type is defined as the set of | ||
* sensitive answer data and then intersected with | ||
* `Perseus<Widget>ValidationData`. | ||
* * `Perseus<Widget>ScoringData` (nee `Perseus<Widget>Rubric`): the data | ||
* needed to score the user input. By convention, this type is defined as | ||
* the set of sensitive answer data and then intersected with | ||
* `Perseus<Widget>ValidationData`. | ||
* | ||
* For example: | ||
* ``` | ||
|
@@ -42,6 +43,7 @@ import type { | |
PerseusOrdererWidgetOptions, | ||
PerseusRadioChoice, | ||
PerseusGraphCorrectType, | ||
MakeWidgetMap, | ||
} from "./perseus-types"; | ||
import type {Relationship} from "./widgets/number-line/number-line"; | ||
|
||
|
@@ -233,93 +235,95 @@ export type PerseusTableScoringData = { | |
|
||
export type PerseusTableUserInput = ReadonlyArray<ReadonlyArray<string>>; | ||
|
||
export type ScoringData = | ||
| PerseusCategorizerScoringData | ||
| PerseusDropdownScoringData | ||
| PerseusExpressionScoringData | ||
| PerseusGroupScoringData | ||
| PerseusGradedGroupScoringData | ||
| PerseusGradedGroupSetScoringData | ||
| PerseusGrapherScoringData | ||
| PerseusInputNumberScoringData | ||
| PerseusInteractiveGraphScoringData | ||
| PerseusLabelImageScoringData | ||
| PerseusMatcherScoringData | ||
| PerseusMatrixScoringData | ||
| PerseusNumberLineScoringData | ||
| PerseusNumericInputScoringData | ||
| PerseusOrdererScoringData | ||
| PerseusPlotterScoringData | ||
| PerseusRadioScoringData | ||
| PerseusSorterScoringData | ||
| PerseusTableScoringData; | ||
|
||
export type UserInput = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is moved down below all of the Rubric/Scoring Data types together with the various widget UserInput types. #organizing |
||
| PerseusCategorizerUserInput | ||
| PerseusCSProgramUserInput | ||
| PerseusDropdownUserInput | ||
| PerseusExpressionUserInput | ||
| PerseusGrapherUserInput | ||
| PerseusGroupUserInput | ||
| PerseusIFrameUserInput | ||
| PerseusInputNumberUserInput | ||
| PerseusInteractiveGraphUserInput | ||
| PerseusLabelImageUserInput | ||
| PerseusMatcherUserInput | ||
| PerseusMatrixUserInput | ||
| PerseusNumberLineUserInput | ||
| PerseusNumericInputUserInput | ||
| PerseusOrdererUserInput | ||
| PerseusPlotterUserInput | ||
| PerseusRadioUserInput | ||
| PerseusSorterUserInput | ||
| PerseusTableUserInput; | ||
|
||
export type UserInputMap = {[widgetId: string]: UserInput}; | ||
export interface ScoringDataRegistry { | ||
categorizer: PerseusCategorizerScoringData; | ||
dropdown: PerseusDropdownScoringData; | ||
expression: PerseusExpressionScoringData; | ||
grapher: PerseusGrapherScoringData; | ||
"graded-group-set": PerseusGradedGroupSetScoringData; | ||
"graded-group": PerseusGradedGroupScoringData; | ||
group: PerseusGroupScoringData; | ||
image: PerseusLabelImageScoringData; | ||
"input-number": PerseusInputNumberScoringData; | ||
"interactive-graph": PerseusInteractiveGraphScoringData; | ||
"label-image": PerseusLabelImageScoringData; | ||
matcher: PerseusMatcherScoringData; | ||
matrix: PerseusMatrixScoringData; | ||
"number-line": PerseusNumberLineScoringData; | ||
"numeric-input": PerseusNumericInputScoringData; | ||
orderer: PerseusOrdererScoringData; | ||
plotter: PerseusPlotterScoringData; | ||
radio: PerseusRadioScoringData; | ||
sorter: PerseusSorterScoringData; | ||
table: PerseusTableScoringData; | ||
} | ||
|
||
/** | ||
* A map of scoring data (previously referred to as "rubric"), keyed by | ||
* `widgetId`. This data is used to score a learner's guess for a PerseusItem. | ||
* | ||
* NOTE: The value in this map is intentionally a subset of WidgetOptions<T>. | ||
* By using the same shape (minus any unneeded render data), we are able to | ||
* share functionality that understands how to traverse maps of `widget id` to | ||
* `options`. | ||
*/ | ||
export type ScoringDataMap = { | ||
[Property in keyof ScoringDataRegistry as `${Property} ${number}`]: { | ||
type: Property; | ||
static?: boolean; | ||
options: ScoringDataRegistry[Property]; | ||
}; | ||
}; | ||
|
||
export type ScoringData = ScoringDataRegistry[keyof ScoringDataRegistry]; | ||
|
||
/** | ||
* This is an interface so that it can be extended if a widget is created | ||
* outside of this Perseus package. See `PerseusWidgetTypes` for a full | ||
* explanation. | ||
*/ | ||
interface UserInputRegistry { | ||
categorizer: PerseusCategorizerUserInput; | ||
"cs-program": PerseusCSProgramUserInput; | ||
dropdown: PerseusDropdownUserInput; | ||
expression: PerseusExpressionUserInput; | ||
grapher: PerseusGrapherUserInput; | ||
group: PerseusGroupUserInput; | ||
iframe: PerseusIFrameUserInput; | ||
"input-number": PerseusInputNumberUserInput; | ||
"interactive-graph": PerseusInteractiveGraphUserInput; | ||
"label-image": PerseusLabelImageUserInput; | ||
matcher: PerseusMatcherUserInput; | ||
matrix: PerseusMatrixUserInput; | ||
"number-line": PerseusNumberLineUserInput; | ||
"numeric-input": PerseusNumericInputUserInput; | ||
orderer: PerseusOrdererUserInput; | ||
plotter: PerseusPlotterUserInput; | ||
radio: PerseusRadioUserInput; | ||
sorter: PerseusSorterUserInput; | ||
table: PerseusTableUserInput; | ||
} | ||
|
||
/** A union type of all the widget user input types */ | ||
export type UserInput = UserInputRegistry[keyof UserInputRegistry]; | ||
|
||
/** | ||
* A map of widget IDs to user input types (strongly typed based on the format | ||
* of the widget ID). | ||
*/ | ||
export type UserInputMap = MakeWidgetMap<UserInputRegistry>; | ||
|
||
/** | ||
* deprecated prefer using UserInputMap | ||
*/ | ||
export type UserInputArray = ReadonlyArray< | ||
UserInputArray | UserInput | null | undefined | ||
>; | ||
|
||
export interface ValidationDataTypes { | ||
categorizer: PerseusCategorizerValidationData; | ||
// "cs-program": PerseusCSProgramValidationData; | ||
// definition: PerseusDefinitionValidationData; | ||
// dropdown: PerseusDropdownValidationData; | ||
// explanation: PerseusExplanationValidationData; | ||
// expression: PerseusExpressionValidationData; | ||
// grapher: PerseusGrapherValidationData; | ||
// "graded-group-set": PerseusGradedGroupSetValidationData; | ||
// "graded-group": PerseusGradedGroupValidationData; | ||
group: PerseusGroupValidationData; | ||
// iframe: PerseusIFrameValidationData; | ||
// image: PerseusImageValidationData; | ||
// "input-number": PerseusInputNumberValidationData; | ||
// interaction: PerseusInteractionValidationData; | ||
// "interactive-graph": PerseusInteractiveGraphValidationData; | ||
// "label-image": PerseusLabelImageValidationData; | ||
// matcher: PerseusMatcherValidationData; | ||
// matrix: PerseusMatrixValidationData; | ||
// measurer: PerseusMeasurerValidationData; | ||
// "molecule-renderer": PerseusMoleculeRendererValidationData; | ||
// "number-line": PerseusNumberLineValidationData; | ||
// "numeric-input": PerseusNumericInputValidationData; | ||
// orderer: PerseusOrdererValidationData; | ||
// "passage-ref-target": PerseusRefTargetValidationData; | ||
// "passage-ref": PerseusPassageRefValidationData; | ||
// passage: PerseusPassageValidationData; | ||
// "phet-simulation": PerseusPhetSimulationValidationData; | ||
// "python-program": PerseusPythonProgramValidationData; | ||
plotter: PerseusPlotterValidationData; | ||
// radio: PerseusRadioValidationData; | ||
// sorter: PerseusSorterValidationData; | ||
// table: PerseusTableValidationData; | ||
// video: PerseusVideoValidationData; | ||
|
||
// Deprecated widgets | ||
// sequence: PerseusAutoCorrectValidationData; | ||
} | ||
|
||
/** | ||
|
@@ -328,7 +332,7 @@ export interface ValidationDataTypes { | |
* data that's available in the client (widget options) and server (scoring | ||
* data) and is represented by a group of types known as "validation data". | ||
* | ||
* NOTE: The value in this map is intentionally a subset of WidgetOptions<T>. | ||
* NOTE: The value in this map is intentionally a subset of WidgetOptions<T>. | ||
* By using the same shape (minus any unneeded data), we are able to pass a | ||
* `PerseusWidgetsMap` or ` into any function that accepts a | ||
* `ValidationDataMap` without any mutation of data. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Love this! Thanks for adding a more detailed example!