Skip to content
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

Categorizer: Extract validation out of scoring #1862

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

Myranae
Copy link
Contributor

@Myranae Myranae commented Nov 14, 2024

Summary:

In order to complete scoring server-side, we need to separate out the validation logic from the scoring logic. This work separates those two functions and updates associated tests.

Issue: LEMS-2596

Test plan:

  • Confirm all checks pass
  • Confirm the widget still behaves as it should.

@Myranae Myranae self-assigned this Nov 14, 2024
Copy link
Contributor

github-actions bot commented Nov 14, 2024

npm Snapshot: Published

Good news!! We've packaged up the latest commit from this PR (9437cfd) and published it to npm. You
can install it using the tag PR1862.

Example:

yarn add @khanacademy/perseus@PR1862

If you are working in Khan Academy's webapp, you can run:

./dev/tools/bump_perseus_version.sh -t PR1862

Copy link
Contributor

github-actions bot commented Nov 14, 2024

Size Change: +161 B (+0.01%)

Total Size: 1.29 MB

Filename Size Change
packages/perseus/dist/es/index.js 425 kB +161 B (+0.04%)
ℹ️ View Unchanged
Filename Size
packages/kas/dist/es/index.js 39 kB
packages/keypad-context/dist/es/index.js 760 B
packages/kmath/dist/es/index.js 4.27 kB
packages/math-input/dist/es/index.js 77.9 kB
packages/math-input/dist/es/strings.js 1.79 kB
packages/perseus-core/dist/es/index.js 1.48 kB
packages/perseus-editor/dist/es/index.js 698 kB
packages/perseus-linter/dist/es/index.js 22.2 kB
packages/perseus/dist/es/strings.js 3.57 kB
packages/pure-markdown/dist/es/index.js 3.66 kB
packages/simple-markdown/dist/es/index.js 12.5 kB

compressed-size-action

@Myranae Myranae marked this pull request as ready for review November 15, 2024 15:35
@khan-actions-bot khan-actions-bot requested a review from a team November 15, 2024 15:35
@khan-actions-bot
Copy link
Contributor

khan-actions-bot commented Nov 15, 2024

Gerald

Required Reviewers
  • @Khan/perseus for changes to .changeset/green-ghosts-burn.md, packages/perseus/src/validation.types.ts, packages/perseus/src/widgets/categorizer/categorizer.tsx, packages/perseus/src/widgets/categorizer/score-categorizer.test.ts, packages/perseus/src/widgets/categorizer/score-categorizer.ts, packages/perseus/src/widgets/categorizer/validate-categorizer.test.ts, packages/perseus/src/widgets/categorizer/validate-categorizer.ts

Don't want to be involved in this pull request? Comment #removeme and we won't notify you of further changes.

Comment on lines +27 to +28
// Translatable text; a list of items to categorize. e.g. ["banana", "yellow", "apple", "purple", "shirt"]
items: ReadonlyArray<string>;

This comment was marked as outdated.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We chatted on Slack, but we agreed to model the Rubric like this:

export type PerseusCategorizerRubric = {
     // The correct answers where index relates to the items and value relates
     // to the category.  e.g. [0, 1, 0, 1, 2]
     values: ReadonlyArray<number>;
} PerseusCategorizerValidationData; 

We also agreed to rename the term Rubric to ScoringData in the Server-Side Scoring area in a future task. :)

Copy link
Contributor Author

@Myranae Myranae Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above comment I made was from an older version of the code. I updated Rubric to be the following:

export type PerseusCategorizerScoringData = {
    // The correct answers where index relates to the items and value relates
    // to the category.  e.g. [0, 1, 0, 1, 2]
    values: ReadonlyArray<number>;
} & PerseusCategorizerValidationData;

Is it also okay to rename Rubric as we go and to use the ticket for renaming any we missed? It helps my mental model to have the names updated while I'm working, but I can totally leave the name change to the very end if it's preferred to do them all at once. And if it's preferred to do them all at once, should I revert the name change in this PR?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started renaming things to ScoringData in one of my PRs. Then as I thought about it, I thought it might be more confusing to have a partial migration state in main. I was going to leave things as "Rubric" until we're done this pass of validation splitting and then we could do a single pass over everything to update our terms from Rubric to ScoringData. But I don't think it's a huge deal either way. :)

Copy link
Contributor

@handeyeco handeyeco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good to ship as-is, but could you also get @jeremywiebe to look at this? Since this is one of the earlier ones we're doing, I want to make sure we're on the right track.

Comment on lines 15 to 18
const validationResult = validateCategorizer(userInput, rubric, strings);
if (validationResult) {
return validationResult;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might make things more clear

Suggested change
const validationResult = validateCategorizer(userInput, rubric, strings);
if (validationResult) {
return validationResult;
}
const validationError = validateCategorizer(userInput, rubric, strings);
if (validationError) {
return validationError;
}

Copy link
Contributor Author

@Myranae Myranae Nov 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! We might want to update the language in the table widget to be consistent with this if this is preferred.

import type {PerseusCategorizerRubric} from "../../validation.types";

describe("validateCategorizer", () => {
it("tells the learner its not complete if not selected", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test case for the null return value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added!

Comment on lines 13 to 25
let completed = true;
rubric.items.forEach((_, i) => {
if (userInput.values[i] == null) {
completed = false;
}
});
if (!completed) {
return {
type: "invalid",
message: strings.invalidSelection,
};
}
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is just moved code, but what about something like:

Suggested change
let completed = true;
rubric.items.forEach((_, i) => {
if (userInput.values[i] == null) {
completed = false;
}
});
if (!completed) {
return {
type: "invalid",
message: strings.invalidSelection,
};
}
return null;
let incomplete = rubric.items.some((_, i) => userInput.values[i] == null);
if (incomplete) {
return {
type: "invalid",
message: strings.invalidSelection,
};
}
return null;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm all for simplifying. I'll try to simplify the validation logic in the future if it seems like a straightforward update. Was trying to leave as close to original as possible as we've mentioned not changing the logic, but I think just simplifying what's there is probably fine. Updated :)


function validateCategorizer(
userInput: PerseusCategorizerUserInput,
rubric: PerseusCategorizerRubric,
Copy link
Contributor

@handeyeco handeyeco Nov 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CC @jeremywiebe

I think this highlights a possible need for more even more clarification of types (or maybe just a helper):

(In the examples I use feRubric as the stuff we need to validate and beRubric as the stuff we need to validate and score.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love this! We just have to make sure whatever the info on rubric is that it doesn't convey the answer in any way. This definitely clarifies things.

@Myranae
Copy link
Contributor Author

Myranae commented Nov 18, 2024

@jeremywiebe @handeyeco This should now follow the format we discussed, with rubric now as scoringData and its type as the old rubric type unioned with the validationData type. Re-requested review for one more check.

Comment on lines +27 to +28
// Translatable text; a list of items to categorize. e.g. ["banana", "yellow", "apple", "purple", "shirt"]
items: ReadonlyArray<string>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We chatted on Slack, but we agreed to model the Rubric like this:

export type PerseusCategorizerRubric = {
     // The correct answers where index relates to the items and value relates
     // to the category.  e.g. [0, 1, 0, 1, 2]
     values: ReadonlyArray<number>;
} PerseusCategorizerValidationData; 

We also agreed to rename the term Rubric to ScoringData in the Server-Side Scoring area in a future task. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants