Skip to content

Update Matrix to render with answerless data #2387

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

Merged
merged 36 commits into from
Apr 22, 2025

Conversation

Myranae
Copy link
Contributor

@Myranae Myranae commented Apr 11, 2025

Summary:

Updates the Matrix widget to render using MatrixPublicWidgetOptions instead of the full widget options. Adds a Storybook story showing the widget can render, is interactive, and is scorable when rendered with answerless data. Answerful data is still required for the actual scoring. Tests showing this functionality are also included.

The render section of Matrix still references this.props.answers, but the transform function takes a widgetOptions object without answers and adds in an answers array shaped after the board size. The default props are set for an empty nested array, so I think that's all good. The answers field is also what carries the user input.

Issue: LEMS-2975

Test plan:

  • Confirm all checks pass
  • Confirm widget still acts as expected
  • No other changes should be noticeable

@Myranae Myranae self-assigned this Apr 11, 2025
Myranae added 2 commits April 14, 2025 10:12
…answerless-matrix

# Conflicts:
#	packages/perseus/src/util/test-utils.ts
#	packages/perseus/src/widgets/matrix/matrix.tsx
Copy link
Contributor

github-actions bot commented Apr 14, 2025

Size Change: +23 B (0%)

Total Size: 466 kB

Filename Size Change
packages/perseus-core/dist/es/index.js 18.5 kB -3 B (-0.02%)
packages/perseus/dist/es/index.js 199 kB +26 B (+0.01%)
ℹ️ View Unchanged
Filename Size
packages/kas/dist/es/index.js 20.7 kB
packages/keypad-context/dist/es/index.js 1 kB
packages/kmath/dist/es/index.js 5.98 kB
packages/math-input/dist/es/index.js 98.6 kB
packages/math-input/dist/es/strings.js 1.61 kB
packages/perseus-editor/dist/es/index.js 88.5 kB
packages/perseus-linter/dist/es/index.js 7.05 kB
packages/perseus-score/dist/es/index.js 9.04 kB
packages/perseus-utils/dist/es/index.js 403 B
packages/perseus/dist/es/strings.js 7.49 kB
packages/pure-markdown/dist/es/index.js 1.22 kB
packages/simple-markdown/dist/es/index.js 6.71 kB

compressed-size-action

…t to render with answerless data. Adds tests and stories for answerless rendering.
Copy link
Contributor

github-actions bot commented Apr 14, 2025

npm Snapshot: Published

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

Example:

pnpm add @khanacademy/perseus@PR2387

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

./dev/tools/bump_perseus_version.js -t PR2387

Comment on lines 538 to 540
type RenderProps = MatrixPublicWidgetOptions & {
answers: ReadonlyArray<ReadonlyArray<string>>;
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if this should be up with the other props. ExternalProps doesn't have the same optional properties as MatrixPublicWidgetOptions, so I didn't think I should add RenderProps to it. The typing for answers is a little strange; transform converts it to a string, so the matrix gets rendered with an array of strings, but there's other logic using it as numbers. The matrix editor creates the array with numbers, but then all the interaction is with strings, until you get back to scoring.

Copy link
Member

Choose a reason for hiding this comment

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

I belive the difference between RenderProps and ExternalProps is that RenderProps is supposed to be what appears inside the component (after defaultProps have been applied) and ExternalProps is what callers of this component need to pass in. But TBH I am a little confused about why we need both types here. It seems like maybe we could define type RenderProps = ExternalProps for this component? I will look into this more after lunch.

Copy link
Contributor

Choose a reason for hiding this comment

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

tbh I was very confused by props in Table too. I think for some of these older widgets, the architecture wasn't as clear.

There's some info here: https://github.com/Khan/perseus/blob/main/docs/architecture.md

I think it's:

  • PerseusMatrixWidgetOptions: all the data we store about the specific widget
  • MatrixPublicWidgetOptions: all the answerless data for the pre-scoring rendering
  • RenderProps: the output of the transform which takes PerseusMatrixWidgetOptions (and in the future MatrixPublicWidgetOptions) as input
  • ExternalProps: wraps RenderProps with additional stuff from WidgetOptions*
  • DefaultProps: is the defaults for things that are optional in ExternalProps
  • Props: is the combination of ExternalProps and DefaultProps - it's everything in ExternalProps except optional fields are filled in from DefaultProps

*WidgetOptions is its own thing:

/**
 * The type representing the common structure of all widget's options. The
 * `Options` generic type represents the widget-specific option data.
 */
export type WidgetOptions<Type extends string, Options> = {
    // The "type" of widget which will define what the Options field looks like
    type: Type;
    // Whether this widget is displayed with the values and is immutable.  For display only
    // NOTE: perseus_data.go says this is required even though it isn't necessary.
    static?: boolean;
    // Whether a widget is scored.  Usually true except for IFrame widgets (deprecated)
    // Default: true
    graded?: boolean;
    // The HTML alignment of the widget.  "default" or "block"
    // NOTE: perseus_data.go says this is required even though it isn't necessary.
    alignment?: string;
    // Options specific to the type field of the widget.  See Perseus*WidgetOptions for more details
    options: Options;
    // Only used by interactive child widgets (line, point, etc) to identify the components
    // NOTE: perseus_data.go says this is required even though it isn't necessary.
    key?: number | null;
    // The version of the widget data spec.  Used to differentiate between newer and older content data.
    // NOTE: perseus_data.go says this is required even though it isn't necessary.
    version?: Version;
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright, I didn't make any changes to ExternalProps or Props. Just added in the RenderProps type. I did notice that getUserInput returns an object with an answers property, but changing the naming there requires a lot of changes, so if that's important, might do that in another PR. Otherwise, will be landing this tomorrow :)

@Myranae Myranae marked this pull request as ready for review April 14, 2025 19:31
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Copy link
Member

@benchristel benchristel left a comment

Choose a reason for hiding this comment

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

LGTM! I left some suggestions inline, but nothing blocking.

Comment on lines 538 to 540
type RenderProps = MatrixPublicWidgetOptions & {
answers: ReadonlyArray<ReadonlyArray<string>>;
};
Copy link
Member

Choose a reason for hiding this comment

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

I belive the difference between RenderProps and ExternalProps is that RenderProps is supposed to be what appears inside the component (after defaultProps have been applied) and ExternalProps is what callers of this component need to pass in. But TBH I am a little confused about why we need both types here. It seems like maybe we could define type RenderProps = ExternalProps for this component? I will look into this more after lunch.

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 don't feel super comfortable approving until the AI generated code has tests/comments/etc, but I also don't want to block you. So I'll just leave some comments.

Comment on lines 538 to 540
type RenderProps = MatrixPublicWidgetOptions & {
answers: ReadonlyArray<ReadonlyArray<string>>;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

tbh I was very confused by props in Table too. I think for some of these older widgets, the architecture wasn't as clear.

There's some info here: https://github.com/Khan/perseus/blob/main/docs/architecture.md

I think it's:

  • PerseusMatrixWidgetOptions: all the data we store about the specific widget
  • MatrixPublicWidgetOptions: all the answerless data for the pre-scoring rendering
  • RenderProps: the output of the transform which takes PerseusMatrixWidgetOptions (and in the future MatrixPublicWidgetOptions) as input
  • ExternalProps: wraps RenderProps with additional stuff from WidgetOptions*
  • DefaultProps: is the defaults for things that are optional in ExternalProps
  • Props: is the combination of ExternalProps and DefaultProps - it's everything in ExternalProps except optional fields are filled in from DefaultProps

*WidgetOptions is its own thing:

/**
 * The type representing the common structure of all widget's options. The
 * `Options` generic type represents the widget-specific option data.
 */
export type WidgetOptions<Type extends string, Options> = {
    // The "type" of widget which will define what the Options field looks like
    type: Type;
    // Whether this widget is displayed with the values and is immutable.  For display only
    // NOTE: perseus_data.go says this is required even though it isn't necessary.
    static?: boolean;
    // Whether a widget is scored.  Usually true except for IFrame widgets (deprecated)
    // Default: true
    graded?: boolean;
    // The HTML alignment of the widget.  "default" or "block"
    // NOTE: perseus_data.go says this is required even though it isn't necessary.
    alignment?: string;
    // Options specific to the type field of the widget.  See Perseus*WidgetOptions for more details
    options: Options;
    // Only used by interactive child widgets (line, point, etc) to identify the components
    // NOTE: perseus_data.go says this is required even though it isn't necessary.
    key?: number | null;
    // The version of the widget data spec.  Used to differentiate between newer and older content data.
    // NOTE: perseus_data.go says this is required even though it isn't necessary.
    version?: Version;
};

Myranae added 20 commits April 16, 2025 14:25
Specifically item to renderer as a renderer is what's being created
…answerless-matrix

# Conflicts:
#	packages/perseus/src/util/test-utils.test.ts
#	packages/perseus/src/widgets/categorizer/categorizer.test.ts
#	packages/perseus/src/widgets/matrix/matrix.tsx
…answerless-matrix

# Conflicts:
#	packages/perseus/src/util.ts
@Myranae Myranae merged commit aa7b1b6 into main Apr 22, 2025
8 checks passed
@Myranae Myranae deleted the tb/LEMS-2975/answerless-matrix branch April 22, 2025 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants