Skip to content

UXE-685: Migrate VariantHistory page to Via - #1925

Merged
tsck merged 14 commits into
evergreen-ci:mainfrom
tsck:UXE-685
Sep 22, 2026
Merged

tsck merged 14 commits into
evergreen-ci:mainfrom
tsck:UXE-685

Conversation

@tsck

@tsck tsck commented Sep 2, 2026 •

Copy link
Copy Markdown
Collaborator

https://jira.mongodb.org/browse/UXE-685

Summary

  • Migrate the VariantHistory page from LeafyGreen to Via.
  • Convert its styling from Emotion to CSS modules.

Screenshots

No Filters

Before

BEFORE

After

AFTER

With Filters

Before

BEFORE-filtered

After

AFTER-filtered

Loading

Before

Before - loading

After

After - loading

Testing

  • Unit tests, type-check, lint, and build pass locally.
  • Playwright and Chromatic run on CI.

@tsck
tsck marked this pull request as ready for review September 9, 2026 13:21
@tsck
tsck requested a review from a team as a code owner September 9, 2026 13:21
@tsck tsck added the spruce label Sep 9, 2026
Comment thread apps/spruce/src/pages/variantHistory/TaskSelector.tsx
Comment thread apps/spruce/src/pages/variantHistory/index.tsx
Comment thread apps/spruce/src/pages/variantHistory/index.tsx
{taskNamesForBuildVariant?.map((taskName) => (
<ComboboxOption key={taskName} value={taskName} />
{(taskNamesForBuildVariant ?? []).map((taskName) => (
<ComboboxItem key={taskName} id={taskName}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is it possible to have the dropdown (but not the parent input) take up the full width of the options? I think that was an option on LG. This is relevant here because often tasks have the same prefix with a different ending e.g. to indicate different hardware or OS versions.
Image

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is kind of interesting. The LG combobox did have an dropdownWidthBasis prop that could be set to option or trigger. option would have made the width grow to the size of the largest option. However, trigger was the default and this prop was never actually set in Spruce. So before this change I believe that both the trigger and the options would have always been set to 300px I believe. If I'm missing something though and this isn't true, definitely let me know please!

Unfortunately dropdownWidthBasis is no longer a prop in Via. We could probably set some sort of max width in CSS, maybe? But I do think this is how it rendered previously if I'm no mistaken.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Interesting, that seems true but I guess the wrap worked better in LG (live here)
image

Giving some more width to the component makes sense though! And should the dropdown items handle wrap better, or maybe we could apply some word break css?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Oh, that's a really good callout on the wrapping! Yes! I think maybe it should. Let me look into this on the Via side. Will update here with what I find.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I've added a ticket to fix this in Via https://jira.mongodb.org/browse/UXE-994. If it doesn't get in before this merge, I'll make sure to bump the version here once it's published

<div className={styles.container}>
<LabelCellContainer>
<ListSkeleton />
<Skeleton isLoading>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I feel like having isLoading always true is a bit of an antipattern; shouldn't this value be used to toggle its visibility?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think it depends on how the component gets used. For Via's Skeleton, isLoading has to be true for the shimmer to apply, so when it's wrapping real content that will eventually load, you'd tie it to that loading state.

Here LoadingRow is only ever placeholder lines, there's nothing real to toggle to. The loading logic lives a level up in HistoryTable and this only mounts while loading is true.

If there was something we could wrap, we could drop LoadingSection altogether and wrap the real rows in <Skeleton isLoading={loading}>, but I don't think that's possible. I think that's why we have the arbitrary placeholder. Please correct me if I'm wrong!

Replace the LG TextInput wrapper in HistoryTableTestSearch with Via SearchField so both header inputs share Via's label typography, 32px height and border tokens.

ComboboxItem needs an explicit textValue: Via runs children through composeRenderProps to append the checkmark, so RAC cannot infer textValue from the string child and every chip rendered empty.

Move FilterChips under the search input it belongs to. Offset is 8px to match the combobox, whose chips clear the input by the Field gap (4px) plus comboBoxChipGroup margin (4px). Page width moves to searchColumn so the shared component no longer dictates its own page geometry.
Reverts the header-column placement only. FilterChips relies on wrapping behavior it gets from the full-width row; constraining it to a 40% column loses that. Chip placement goes to design before any further change.
@tsck
tsck requested a review from sophstad September 14, 2026 16:22
@tsck

tsck commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

@sophstad This is mostly ready for another look. Waiting on input regarding the page design, but should be good from a code feedback standpoint!

Comment thread apps/spruce/src/pages/variantHistory/index.tsx

return (
<RowContainer>
<div className={styles.rowContainer}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nit] I noticed Field in the Via Storybook, is this something worth using here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Field is technically marked @internal and I don't think is exported from the package root. This is confusing because it's in the storybook, sorry about that! We're working on fixing up the Storybook as we speak 🙂

That said, I don't believe it would actually apply here. It's really meant to wrap an input that uses React Aria Components under the hood.

{taskNamesForBuildVariant?.map((taskName) => (
<ComboboxOption key={taskName} value={taskName} />
{(taskNamesForBuildVariant ?? []).map((taskName) => (
<ComboboxItem key={taskName} id={taskName}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Interesting, that seems true but I guess the wrap worked better in LG (live here)
image

Giving some more width to the component makes sense though! And should the dropdown items handle wrap better, or maybe we could apply some word break css?

!inactive && (!!loadingTestResults || failingTests.length > 0);

return (
<TooltipRoot align="center" isDisabled={!showTooltip} side="right">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The tooltip show animation is kinda flickery when looking at a tooltip on Firefox, are you able to reproduce this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't think I'm seeing this! Any chance you could take a short screen recording to capture what you're seeing on your end?

@@ -38,22 +38,26 @@ const ColumnPaginationButtons: React.FC<ColumnPaginationButtonProps> = ({
return (
<div className={styles.container}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Optional but moving the pagination buttons inline with the other input elements seems reasonable to me

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Makes sense. I'll update this with the redesign!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@sophstad I tried this but it caused some weird UI in smaller browser. I chose to just leave it as is, but let me know if you want anything adjusted!

Comment thread apps/spruce/src/pages/variantHistory/TaskSelector.tsx
@@ -91,13 +91,8 @@ const ColumnHeaders: React.FC<ColumnHeadersProps> = ({
// eslint-disable-next-line react/no-array-index-key
<LoadingCell key={`loading_cell_${i}`} isHeader />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we actually need to use loaders here? If task names are selected we should know the number of columns without waiting for any data, and if not maybe we could just keep things simple and omit? This is just being nitpicky/interested in details, though, feel free to ignore 😝

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

That's a good call out. columnLimit is just DEFAULT_COLUMN_LIMIT rather than the actual selected column count, so the skeletons can overshoot what ends up rendering.

I'm inclined to leave it here though, since this block is untouched by the migration and BaseRow falls back to columnLimit the same way. Changing just the header would desync the two. Totally your call though if you want to remove them!

Chips pushed the table down as tasks were selected. showChips={false} drops the chip row; Via 0.8.6 replaces it with a selection count in the closed field (UXE-839, via#594), which lands regardless of showChips.

Bump needs tokens 0.3.4 alongside components 0.8.6. Carries two visual changes beyond this page: focus-ring offset (0.8.4) and the text.link contrast fix, so Chromatic drift outside variantHistory is expected.

TaskSelector test asserted a chip; now asserts the count summary and the URL round-trip.
# Conflicts:
#	apps/parsley/package.json
#	apps/spruce/package.json
#	apps/spruce/src/components/HistoryTable/ColumnPaginationButtons.tsx
#	pnpm-lock.yaml
@tsck
tsck requested a review from sophstad September 16, 2026 15:55
@tsck

tsck commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator Author

@sophstad This should be ready for another look. I've updated the screenshots in the description to represent the new page states

# Conflicts:
#	apps/parsley/package.json
#	apps/spruce/package.json
#	packages/lib/package.json
#	pnpm-lock.yaml
Via's Combobox renders role=combobox, not role=textbox, so the Tasks
locator never resolved. Via tooltips are React Aria based and ignore
hover until a pointer event establishes pointer modality, so a bare
.hover() on a fresh page never opened them - same reason the unit test
already calls primePointerModality(). Verified both against the real
Storybook DOM: new locator resolves (old resolves to 0), hoverForTooltip
opens the tooltip (bare .hover() does not).
trigger={
<Body onClick={onClick} weight="medium">
<TooltipRoot align="center" side="top">
<TooltipTrigger>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I tried to take a video to show the flicker behavior but now I'm having trouble getting them to appear at all on the failing tasks. This was working previously 🤔
Image

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Are we sure that task has failed tests attached to it?

I'm having a hard time replicating this, and I could be missing something, but the logic that enables the tooltip is the same on this branch as it is on main:

  • main: enabled={!inactive && (loadingTestResults || failingTests.length > 0)}
  • branch: isDisabled={!(!inactive && (!!loadingTestResults || failingTests.length > 0))}

Same inputs, same result, so anything that shows a tooltip on main should show one here too.

The tooltip itself does render. I can see it in this branch's published Storybook. That story hardcodes failingTests though, so it only proves the component works, not the data path.

For the data path I think staging is the better comparison since it's running main. I don't get tooltips there either: https://spruce-staging.corp.mongodb.com/variant-history/zackary-bisect/s3put-public-read. None of those failed tasks have failed tests attached to them. Same thing locally. So the tooltip is correctly disabled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

wow i'm really sorry, i looked at a couple tasks that just happened to not have failing tests 😭 this does seem to be working correctly!

@tsck
tsck requested a review from sophstad September 20, 2026 18:34
trigger={
<Body onClick={onClick} weight="medium">
<TooltipRoot align="center" side="top">
<TooltipTrigger>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

wow i'm really sorry, i looked at a couple tasks that just happened to not have failing tests 😭 this does seem to be working correctly!

@tsck
tsck merged commit 9cfea79 into evergreen-ci:main Sep 22, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants