-
Notifications
You must be signed in to change notification settings - Fork 853
Forms: Display star rating icons in response inspector #46874
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
Conversation
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! Jetpack plugin: No scheduled milestone found for this plugin. If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack. |
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.
Pull request overview
Adds a dedicated rating-field renderer for the Forms response inspector so rating submissions can be displayed as icons instead of plain text.
Changes:
- Add a new
FieldRatingcomponent that renders rating icons. - Wire
FieldRatinginto the response inspector field preview forratingfields. - Add a changelog entry documenting the UI enhancement.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
projects/packages/forms/src/dashboard/components/inspector/response-fields/field-rating/index.tsx |
Introduces the FieldRating renderer responsible for displaying rating icons. |
projects/packages/forms/src/dashboard/components/inspector/response-fields/field-preview/index.tsx |
Integrates FieldRating into the response-field value rendering flow for rating types. |
projects/packages/forms/changelog/add-forms-field-rating-preview |
Documents the new rating icon display in the response inspector. |
...ts/packages/forms/src/dashboard/components/inspector/response-fields/field-preview/index.tsx
Show resolved
Hide resolved
...cts/packages/forms/src/dashboard/components/inspector/response-fields/field-rating/index.tsx
Outdated
Show resolved
Hide resolved
...cts/packages/forms/src/dashboard/components/inspector/response-fields/field-rating/index.tsx
Outdated
Show resolved
Hide resolved
...cts/packages/forms/src/dashboard/components/inspector/response-fields/field-rating/index.tsx
Show resolved
Hide resolved
Code Coverage SummaryCoverage changed in 3 files.
2 files are newly checked for coverage.
Full summary · PHP report · JS report Coverage check overridden by
Coverage tests to be added 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.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| parsedMax < 0 | ||
| ) { | ||
| return <>-</>; | ||
| } | ||
| const displayRating = Math.min( Math.max( 0, parsedRating ), parsedMax ); |
Copilot
AI
Jan 30, 2026
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.
displayRating is currently clamped to parsedMax, so an invalid stored value like "7/5" would be rendered as 5 stars. This hides data problems and diverges from the backend validation in src/contact-form/class-feedback-field.php (it treats rating > max and max <= 0 as invalid and returns the raw string). Consider changing the validation to return "-" when parsedMax <= 0 or parsedRating > parsedMax (instead of clamping) so the inspector doesn’t display a misleading rating.
| parsedMax < 0 | |
| ) { | |
| return <>-</>; | |
| } | |
| const displayRating = Math.min( Math.max( 0, parsedRating ), parsedMax ); | |
| parsedMax <= 0 || | |
| parsedRating > parsedMax | |
| ) { | |
| return <>-</>; | |
| } | |
| const displayRating = parsedRating; |
| it( 'clamps rating to max (e.g. 7/5 shows 5 icons)', () => { | ||
| const { container } = render( <FieldRating value="7/5" /> ); | ||
|
|
||
| // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access | ||
| const svgs = container.querySelectorAll( 'svg' ); | ||
| expect( svgs ).toHaveLength( 5 ); |
Copilot
AI
Jan 30, 2026
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.
This test asserts that an invalid value "7/5" should be clamped to 5 icons, but the backend formatter treats rating > max as invalid and would not consider this a valid rating. The test should be updated to match the desired/validated behavior (e.g., expecting a fallback like "-"), otherwise it will lock in incorrect UI behavior.
| it( 'clamps rating to max (e.g. 7/5 shows 5 icons)', () => { | |
| const { container } = render( <FieldRating value="7/5" /> ); | |
| // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access | |
| const svgs = container.querySelectorAll( 'svg' ); | |
| expect( svgs ).toHaveLength( 5 ); | |
| it( 'falls back to "-" when rating exceeds max (e.g. "7/5")', () => { | |
| render( <FieldRating value="7/5" /> ); | |
| expect( screen.getByText( '-' ) ).toBeInTheDocument(); |
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 don't think invalidating the entire value or coercing to "-" would entirely make sense. Maybe set to max (5/5) on those cases? Wouldn't help much the user to just find a dash if someone has achieved to mess with the submit value, it would only create questions.
projects/packages/forms/tests/js/dashboard/components/response-view/field-rating/index.test.jsx
Outdated
Show resolved
Hide resolved
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.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
...cts/packages/forms/src/dashboard/components/inspector/response-fields/field-rating/index.tsx
Outdated
Show resolved
Hide resolved
| <SVG viewBox="0 0 24 24" aria-hidden="true"> | ||
| <Path | ||
| d={ RATING_ICONS.stars } | ||
| fill="#F0B849" | ||
| stroke="#F0B849" | ||
| strokeWidth="0" | ||
| strokeLinejoin="round" | ||
| /> |
Copilot
AI
Jan 31, 2026
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.
The SVG path styling is hard-coded to #F0B849, which may not work well with themes/dark mode and is inconsistent with existing rating icons that use currentColor (e.g. src/blocks/input-rating/edit.js:18-25). Consider switching to currentColor + a wrapper class/CSS variable for the color so the inspector rendering follows established styling patterns.
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 use the renderRatingIconsHtml helper too, instead of just RATING_ICONS? It's a bit problematic otherwise that Path and its props are defined separately in two different places.
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.
Mmm... but renderRatingIconsHtml was designed to work on frontend, hence it outputs an html string. The approach used here is the same used on the editor context. I don't see how using renderRatingIconsHtml would help us in this context.
We could include in the helper a version that outputs a react element though.
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.
Gotcha! Then it might make sense to keep it like so. Still, to me it looks odd.
...cts/packages/forms/src/dashboard/components/inspector/response-fields/field-rating/index.tsx
Show resolved
Hide resolved
projects/packages/forms/tests/js/dashboard/components/response-view/field-rating/index.test.jsx
Outdated
Show resolved
Hide resolved
| }; | ||
|
|
||
| // Maximum icons to render to prevent DOM bloat from malformed values | ||
| const MAX_RATING_ICONS = 10; |
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.
Might be a good constant to share with the block edit function so that they never diverge accidentally:
| max={ 10 } |
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 move it over to the block then and import it from there
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.
Moved things a bit around, so exports needed from the module stay on slim files yet the RatingIcon itself lives on its own
| const FieldRating = ( { value }: FieldRatingProps ) => { | ||
| const stringValue = value != null ? String( value ) : ''; | ||
| if ( stringValue.trim() === '' ) { | ||
| return <>-</>; |
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.
Why <></> here?
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.
Failsafe mechanism for empty values. It should never happen, but it was the consensus when any field doesn't have a value. If there's nothing to show, bail out early.
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 I meant why <>-</> not just -? :-)
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.
Gotcha! Yeah, no reason. Simplified it.
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.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
| import { RatingIcon } from '../../../../../blocks/input-rating/edit.js'; | ||
|
|
Copilot
AI
Feb 2, 2026
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.
FieldRating imports RatingIcon from blocks/input-rating/edit.js, but that module also imports @wordpress/block-editor and editor-only hooks. Pulling the whole editor "edit" module into the dashboard/response inspector bundle is likely to break in contexts where @wordpress/block-editor isn’t present, and it also increases bundle size unnecessarily. Please move RatingIcon into a small shared module (no block-editor deps) and import it from both edit.js and this dashboard component.
| import { RatingIcon } from '../../../../../blocks/input-rating/edit.js'; | |
| type RatingIconProps = { | |
| iconStyle?: 'stars'; | |
| strokeColor?: string; | |
| fillColor?: string; | |
| }; | |
| const RatingIcon = ( { | |
| // Keep signature compatible with potential future extensions. | |
| iconStyle = 'stars', | |
| strokeColor = 'currentColor', | |
| fillColor = 'none', | |
| }: RatingIconProps ) => { | |
| // Currently only "stars" style is used in this context. | |
| if ( iconStyle !== 'stars' ) { | |
| return null; | |
| } | |
| return ( | |
| <svg | |
| width="24" | |
| height="24" | |
| viewBox="0 0 24 24" | |
| aria-hidden="true" | |
| role="img" | |
| > | |
| <path | |
| d="M12 2.5l2.694 5.458 6.026.876-4.36 4.25 1.029 6.006L12 16.79l-5.389 2.8 1.029-6.006-4.36-4.25 6.026-.876L12 2.5z" | |
| fill={ fillColor } | |
| stroke={ strokeColor } | |
| strokeWidth="1.2" | |
| strokeLinejoin="round" | |
| /> | |
| </svg> | |
| ); | |
| }; |
| const ratingLabel = sprintf( | ||
| /* translators: 1: rating value, 2: maximum rating (e.g. "4" and "5" for "4 out of 5") */ | ||
| __( 'Rating %1$s out of %2$s', 'jetpack-forms' ), | ||
| String( displayRating ), | ||
| String( clampedMax ) |
Copilot
AI
Feb 2, 2026
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.
The accessible label uses displayRating/clampedMax, which can become inaccurate when the submitted value has a larger max than MAX_RATING_ICONS (e.g. "50/100" will be announced as "Rating 10 out of 10"). Keep clamping only for rendered icon count, but use the original parsed values (or otherwise convey the cap) in the VisuallyHidden text so screen readers get the real rating.
| const ratingLabel = sprintf( | |
| /* translators: 1: rating value, 2: maximum rating (e.g. "4" and "5" for "4 out of 5") */ | |
| __( 'Rating %1$s out of %2$s', 'jetpack-forms' ), | |
| String( displayRating ), | |
| String( clampedMax ) | |
| // Use the original scale in the accessible label so screen readers get the real rating. | |
| const accessibleRating = Math.min( Math.max( 0, parsedRating ), parsedMax ); | |
| const ratingLabel = sprintf( | |
| /* translators: 1: rating value, 2: maximum rating (e.g. "4" and "5" for "4 out of 5") */ | |
| __( 'Rating %1$s out of %2$s', 'jetpack-forms' ), | |
| String( accessibleRating ), | |
| String( parsedMax ) |
| if ( | ||
| ! Number.isFinite( parsedRating ) || | ||
| parsedRating < 0 || | ||
| ! Number.isFinite( parsedMax ) || | ||
| parsedMax < 0 | ||
| ) { | ||
| return <>-</>; | ||
| } | ||
|
|
||
| // Clamp max to prevent DOM bloat from large values | ||
| const clampedMax = Math.min( parsedMax, MAX_RATING_ICONS ); | ||
| const displayRating = Math.min( Math.max( 0, parsedRating ), clampedMax ); | ||
|
|
Copilot
AI
Feb 2, 2026
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.
parsedMax of 0 currently renders an empty HStack (0 icons) with only a visually-hidden label. Consider treating max <= 0 as invalid and falling back to "-" to avoid an apparently blank value in the UI (e.g. input "0/0" or "3/0").
f384ddf to
0fcf737
Compare
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.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
| return ( | ||
| <> | ||
| <VisuallyHidden as="span">{ ratingLabel }</VisuallyHidden> | ||
| <HStack spacing="1" alignment="topLeft"> |
Copilot
AI
Feb 2, 2026
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.
The alignment value "topLeft" should be "left" for HStack. The WordPress HStack component accepts alignment values like "left", "center", "right", "top", "bottom" but not "topLeft". This may cause unexpected behavior or styling issues.
| <HStack spacing="1" alignment="topLeft"> | |
| <HStack spacing="1" alignment="left"> |
- Validate rating and max values are finite and non-negative - Clamp rating to max value to prevent overflow (e.g., 7/5 shows 5 stars) - Fall back to "-" for non-numeric values Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Move RatingIcon component from input-rating/edit.js to a dedicated rating-icon.js file to allow importing without editor dependencies. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Move the max rating icons limit to rating-icons.js as a shared constant. Update field-rating/edit.js to import and use it. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Import RatingIcon and MAX_RATING_ICONS from the shared modules instead of the block editor files to avoid pulling in unnecessary dependencies. Co-Authored-By: Claude Opus 4.5 <[email protected]>
5cff204 to
b6d1cd8
Compare

Proposed changes:
Display star rating icons in the response inspector instead of plain text for rating field submissions.
FieldRatingcomponent that renders star icons based on the rating valueFieldRatinginto the field preview component for rating field typesBefore

After

Includes also:
refactor the rating icon SVG into a reusable RatingIcon component:
edit.js - Extracted the inline SVG into an exported RatingIcon component that accepts:
field-rating/index.tsx - Replaced the duplicate filledIcon and emptyIcon SVG definitions with the new RatingIcon component, passing appropriate colors for filled (#F0B849) vs empty states.
This reduces code duplication by ~22 lines while making the rating icon reusable across both the block editor and the response inspector dashboard.
Other information:
Jetpack product discussion
N/A
Does this pull request change what data or activity we track or use?
No
Testing instructions: