Fleet UI: APRF library item accordion component - #47944
Conversation
Adds the presentational LibraryItemAccordion for the Library section of SoftwareTitleDetailsPage. Fully prop-driven with Storybook and Jest coverage. Includes a temporary preview render above SoftwareInstallerCard (tagged `TODO #47622 preview`) so design states can be reviewed end-to-end; removed before this PR merges.
- Status counts (installed/pending/failed) link to the same host list pages as the installer card's status table - Label-count tooltip lists each label on its own line under the "Include any/all" / "Exclude any" heading - Badge clicks no longer toggle the accordion (stopPropagation) - Wire the preview block in SoftwareTitleDetailsPage to real software_package data (labels, status counts, hash, download)
Wires App Store and Play Store branches through the accordion by embedding the existing widget instead of duplicating icon + version logic. Adds hideInstallerType + disableTitleTooltip to the widget, refactors the details row to a parts-array join so suppressing chips never leaves a stray middot, and raises the title's max-width to 60vw (48vw below $break-md).
- Add LibraryItemAccordionList Storybook coverage (8 scenarios across installer types, multi-version listings, and pin states) with reactive controls for label scope, label count, and the Latest/Pinned/Major version badge. - Add a third "Major version" badge variant to LibraryItemAccordion (distinct label, same pin icon, mutually exclusive with isLatest/isPinned). - Make status-count paths required and always render them as CustomLinks; drop the plain-text fallback. - Suppress every InstallerDetailsWidget tooltip (title, version, addedAt, Play Store) when the row is inactive via a unified disableTooltips prop. - Drop the inline SHA chip + copy logic from InstallerDetailsWidget; the accordion owns the SHA display in its expanded panel now. - Bump Storybook canvas padding to 90px so previews mirror the real page.
- Collapse `isLatest` / `isPinned` / `isMajorVersionPinned` booleans + their
three click callbacks into a single discriminated union: `badgeState?:
"latest" | "pinned" | "majorVersion"` + `onBadgeClick`. The three badge
states were documented as mutually exclusive but unenforced; the type
system now enforces it.
- Add `helpers.ts` with a pure `deriveAccordionRowState({ rowVersion,
activeVersion, pinnedVersion })` function for #47623 to consume. Returns
`{ isActive, badgeState? }` — branches on caret-prefix for major-version
pin. Covered by 8 unit tests.
- Replace the dead `noop` smoke test with one that actually clicks every
interactive element without handlers wired.
- Add missing coverage: per-state `onBadgeClick` (Latest/Pinned/Major
version) via parameterized test, `stopPropagation` on the badge,
onLabelCountClick, onDownloadClick, canEditSoftware → static label
badge, download button hidden without `downloadUrl`, status counts
render as links with the correct hrefs, no-badge case.
- Document that `disableTooltips={!isActive}` on inactive rows is
intentional — the row already wraps in the rollback tooltip and Fleet UI
avoids stacking two tooltips on the same hover target.
- Update production caller in `SoftwareTitleDetailsPage.tsx` to use
`badgeState="latest"`.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #47944 +/- ##
==========================================
+ Coverage 67.20% 67.24% +0.04%
==========================================
Files 3630 3652 +22
Lines 229488 230309 +821
Branches 11807 12302 +495
==========================================
+ Hits 154218 154869 +651
- Misses 61408 61573 +165
- Partials 13862 13867 +5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@claude review |
| return ( | ||
| <> | ||
| {renderSoftwareSummaryCard(softwareTitle)} | ||
| {renderLibraryItemAccordionPreview(softwareTitle)} | ||
| {renderSoftwareInstallerCard(softwareTitle)} | ||
| {renderLibraryEditModal(softwareTitle)} | ||
| </> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🔴 The renderLibraryItemAccordionPreview + renderLibraryEditModal block is unconditionally rendered from renderContent (lines 388, 390) despite the author's explicit TODO #47622 preview — remove before merging into main markers on the helper (L212), the showLibraryEditModal state (L199), and the useSoftwareInstaller call (L224). If this merges as-is, every user visiting /software/titles/:id will see (1) the new LibraryItemAccordion stacked on top of the existing SoftwareInstallerCard rendering the same installer details twice, and (2) for custom-package titles, a hardcoded fake row at L316-343 with the developer placeholder filename example-package-v2-really-long-package-name-to-see-what-happens-responsive-design.pkg, version 2.0.0, addedAt: 2024-01-01, and install/pending/failed counts duplicated from the real row. Additionally the preview never wires onTrashClick, onDownloadClick, or onBadgeClick, so the trash, download, and "Latest" badge buttons render but silently no-op. Per the PR description, multi-row integration belongs to #47623 — drop both render helpers, their call sites, the showLibraryEditModal state, and the imports added solely for the preview (useSoftwareInstaller, EditSoftwareModal, getDisplayedSoftwareName, ILabelSoftwareTitle).
Extended reasoning...
What's wrong
The PR scope is "new LibraryItemAccordion component + Storybook + unit tests" (per the PR description, closing #47622). However, the diff to SoftwareTitleDetailsPage.tsx also wires the accordion into the production renderContent() output as a side-by-side preview that the author explicitly marked for removal:
// SoftwareTitleDetailsPage.tsx:384-393
if (softwareTitle) {
return (
<>
{renderSoftwareSummaryCard(softwareTitle)}
{renderLibraryItemAccordionPreview(softwareTitle)} // NEW preview block
{renderSoftwareInstallerCard(softwareTitle)} // EXISTING card
{renderLibraryEditModal(softwareTitle)} // NEW modal
</>
);
}The author left three explicit TODOs flagging this:
- L199:
TODO #47622 preview — page-level state for opening the EditSoftwareModal from the LibraryItemAccordion. Remove with the preview block. - L212:
TODO #47622 preview — remove before merging into main. - L224 (above the
useSoftwareInstallercall): same intent.
Step-by-step proof of what users would see
Consider a Fleet admin visiting /software/titles/:id for a title backed by a custom .pkg (the most common case — anything uploaded via Add Software in the UI).
renderSoftwareSummaryCardruns — produces the existing top card with the title's display name, icon, host counts.renderLibraryItemAccordionPreviewruns (L212-345) — falls through theappStorebranch (noapp_store_app) and renders<LibraryItemAccordionList>with two accordion rows:- Row 1 (L276-314): real data from
pkg(pkg.name,pkg.version,pkg.uploaded_at, real status counts, realhash_sha256, realpkg.urlfor download).isActive,badgeState="latest". - Row 2 (L316-343): hardcoded placeholder —
filename="example-package-v2-really-long-package-name-to-see-what-happens-responsive-design.pkg",version="2.0.0",addedAt="2024-01-01T12:00:00Z".isActive={false}so it greys out, but the install/pending/failed counts copypkg.status?.installed ?? 0etc. from the real row, so the same numbers appear twice.
- Row 1 (L276-314): real data from
renderSoftwareInstallerCardruns — produces the existingSoftwareInstallerCardwhich surfaces the same filename, the same version, the same installer-type label, the same status table, and the same hash with copy button (the new accordion now owns the hash chip, butSoftwareInstallerCardstill renders the full installer details via its own<InstallerDetailsWidget>).
End result on screen, top to bottom: SoftwareSummaryCard → LibraryItemAccordion (real row + obviously-fake placeholder row stacked together) → SoftwareInstallerCard (real installer info again). Three panels of overlapping information instead of one cohesive view, with one of them showing literal developer QA copy (really-long-package-name-to-see-what-happens-responsive-design).
Why the type system doesn't catch this
canEditSoftware (required prop) is supplied via the new canWriteSoftware helper, and onLabelCountClick/onLabelsClick are wired to setShowLibraryEditModal(true). So the preview block compiles and runs without warnings — there's no missing-prop error to flag the half-finished integration. The TODO comments are the only signal that the block isn't supposed to ship, and the PR was submitted with them intact.
Worth flagging too: onTrashClick, onDownloadClick, and onBadgeClick are never passed to either accordion in the preview. The component still renders those buttons (download is gated only by downloadUrl which equals pkg.url and is truthy; trash is gated by canEditSoftware; the "Latest" badge is gated by badgeState). The buttons exist visually but are no-ops on click — exactly the kind of broken-but-not-crashy UX a PR review needs to catch.
How to fix it
Mechanical removal (the author already pre-tagged what needs to go):
- Delete the
renderLibraryItemAccordionPreviewhelper (L212-345) and its call site at L388. - Delete the
renderLibraryEditModalhelper (L347-367) and its call site at L390. - Delete the
showLibraryEditModalstate at L199 and theuseSoftwareInstaller(softwareTitle ?? ...)call at L224. - Drop the imports added solely for this preview:
useSoftwareInstaller,ILabelSoftwareTitle,EditSoftwareModal,getDisplayedSoftwareName, and theLibraryItemAccordion+LibraryItemAccordionList+LibraryItemLabelKindimports.
The canWriteSoftware helper itself and the new canEditSoftware derivation can stay — they're independently useful (and the helper has its own unit tests in this PR), and #47623 will need them when it does the real integration. The new LibraryItemAccordion component, its tests, its Storybook stories, and the InstallerDetailsWidget cleanup all stay — those are the legitimate scope of this PR.
Addressing the refutation (bug_003 marked as duplicate)
Two verifiers suggested bug_003 is a duplicate of bug_002. The synthesis agent has already merged them into a single finding here — this comment covers both the hardcoded placeholder row (the bug_002 framing) and the structural double-render + unwired handlers (the bug_003 framing). The fix is identical for both: remove the preview block entirely. No fragmentation.
There was a problem hiding this comment.
This is intended for the draft branch until we wire it up to the API
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
There was a problem hiding this comment.
Pull request overview
Adds a new “Library item accordion” UI surface for Software Title details, plus supporting utilities (permissions helper, truncation/tooltip behaviors, icons) and Storybook/Jest coverage to validate the accordion’s many visual/interaction states.
Changes:
- Introduces
LibraryItemAccordion(+ list wrapper, helpers, styles, stories, tests) to render collapsed/expanded “library version” rows with badges, labels, status counts, hash-copy, download, and trash actions. - Adds
canWriteSoftware(user, teamId)permission helper (and tests) to gate edit/delete affordances consistently across global/team roles. - Refactors
InstallerDetailsWidgetto support “disable all tooltips” + “hide installer type” for embedding in the accordion, and updates Storybook configuration/assets to include these new stories.
Reviewed changes
Copilot reviewed 30 out of 30 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
frontend/utilities/permissions/permissions.ts |
Adds canWriteSoftware helper and exports it via the default permissions object. |
frontend/utilities/permissions/permissions.tests.ts |
Adds unit tests for canWriteSoftware across global/team roles. |
frontend/test/storybook-utils.tsx |
Adds a shared Storybook decorator for consistent framed rendering. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsPage.tsx |
Wires a temporary accordion “preview” + edit modal into the production page render. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/SoftwareInstallerCard.tsx |
Stops passing sha256 into InstallerDetailsWidget (sha display moved elsewhere). |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/InstallerDetailsWidget/InstallerDetailsWidget.tsx |
Adds hideInstallerType + disableTooltips, removes SHA display/copy, refactors detail rendering. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/InstallerDetailsWidget/InstallerDetailsWidget.tests.tsx |
Removes SHA-related test and keeps remaining coverage aligned with refactor. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/InstallerDetailsWidget/_styles.scss |
Removes SHA/copy styles and adjusts title max-width responsiveness. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordionList.tsx |
Adds a thin list wrapper component for accordion rows. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordionList.stories.tsx |
Adds multi-row Storybook scenarios with controls-driven cloning/injection. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tsx |
Adds the main accordion component with badges, tooltips, counts, labels, hash-copy, download/trash actions. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tests.tsx |
Adds Jest/RTL coverage for interactive and visual states of the accordion. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.stories.tsx |
Adds single-row Storybook variants (badge states, inactive, permissions off, etc.). |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/index.ts |
Adds barrel export for the accordion. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/helpers.ts |
Adds pure deriveAccordionRowState helper for pin/latest/major logic. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/helpers.tests.ts |
Adds unit tests covering all helper branches/edge-cases. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/_styles.scss |
Adds accordion + list styles (layout, hover/focus, inactive presentation). |
frontend/components/TruncatedTextList/TruncatedTextList.tsx |
Adds a reusable truncated list component with “+N more” tooltips and optional link-style click. |
frontend/components/TruncatedTextList/TruncatedTextList.stories.tsx |
Adds Storybook stories for truncation behaviors. |
frontend/components/TruncatedTextList/index.ts |
Adds barrel export for TruncatedTextList. |
frontend/components/TruncatedTextList/_styles.scss |
Adds styles for measurement/visible layers and “+N more” affordance. |
frontend/components/TooltipTruncatedText/TooltipTruncatedText.tsx |
Adds disableTooltip prop to suppress tooltips even when truncated. |
frontend/components/icons/Tag.tsx |
Adds new tag icon. |
frontend/components/icons/Pin.tsx |
Adds new pin icon. |
frontend/components/icons/index.ts |
Registers the new pin and tag icons in ICON_MAP. |
frontend/components/Icon/Icon.stories.tsx |
Improves Icon story by making name a selectable control from ICON_MAP. |
frontend/components/buttons/Button/Button.tsx |
Updates variant comments to deprecate text-icon and steer new code to inverse. |
.storybook/preview.scss |
Adjusts Storybook canvas padding globally for more realistic layout. |
.storybook/preview.js |
Imports the new Storybook preview stylesheet. |
.storybook/main.ts |
Includes SoftwareTitleDetailsPage stories in Storybook globs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // TODO #47622 preview — remove before merging into main. | ||
| // Renders a single LibraryItemAccordion from the active software_package or | ||
| // app_store_app so design can review with real data; multi-row rendering | ||
| // lands in #47623. | ||
| const renderLibraryItemAccordionPreview = (title: ISoftwareTitleDetails) => { |
There was a problem hiding this comment.
this is intended for now so the reviewer can see what it looks like on the page
There was a problem hiding this comment.
I considered remove this, but it removed all the plumbing I already had working and wasn't willing to sacrifice that for a "clean" PR
WalkthroughThis PR introduces the Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tests.tsx (1)
140-145: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider using proper type assertion instead of
as never.The
as nevercast completely bypasses TypeScript's type checking. While this works for tests, usingas ILabelSoftwareTitle[](likemakeLabelsdoes on line 49) is more explicit and catches accidental shape mismatches.const labels = [ { id: 1, name: "Design" }, { id: 2, name: "Engineering" }, { id: 3, name: "IT" }, - ] as never; + ] as ILabelSoftwareTitle[];🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tests.tsx` around lines 140 - 145, Replace the `as never` type assertion on the `labels` variable in the test case "renders a tooltip with the label list when hovering the count badge" with a proper type assertion using `as ILabelSoftwareTitle[]` instead. This will ensure TypeScript properly validates the object structure matches the expected interface rather than bypassing type checking completely, making the test more maintainable and catching shape mismatches.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.storybook/preview.scss:
- Around line 6-8: The padding rule applied to `.sb-main-padded.sb-show-main` in
the preview.scss file is too broad and affects all stories globally, causing
unintended layout changes in unrelated previews and snapshots. Remove this
global padding rule from the preview.scss file and instead apply the padding:
90px styling using a targeted decorator or custom class that is scoped only to
the SoftwareTitleDetailsPage stories, ensuring the spacing override affects only
the intended stories.
In `@frontend/components/TruncatedTextList/TruncatedTextList.tsx`:
- Around line 62-85: The truncatedFirstContent rendering currently shows a "+0
more" pill when there is only one item in the list (items.length === 1), which
is incorrect UI behavior. Conditionally render the second TooltipWrapper block
that contains the "+{items.length - 1} more" span only when items.length is
greater than 1, so the "more" pill does not appear when there are no additional
items to display. Apply the same fix to the corresponding location mentioned at
lines 109-110.
- Around line 163-166: The itemRefs.current array can contain stale references
after the items array shrinks, causing totalWidth calculations to be overcounted
with measurements from refs that no longer correspond to current items. This
results in unnecessary label truncation. Fix this by limiting the ref
measurement to only the current items count - either by slicing itemRefs.current
to items.length before mapping, or by iterating only up to items.length. Apply
this same fix to the similar width calculation that occurs around lines 217-223
where the same stale ref issue applies.
In
`@frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tsx`:
- Around line 414-420: The handleHeaderKeyDown function toggles the accordion
even when keyboard events originate from nested elements like badge buttons,
because the event bubbles to the header. Modify handleHeaderKeyDown to check
that the event target is the header element itself (using e.currentTarget or by
comparing e.target to the header ref) before toggling. Only call
toggleExpanded() when the Enter or Space key is pressed and the focus is
directly on the header, not on any nested child elements.
In
`@frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/InstallerDetailsWidget/_styles.scss`:
- Line 9: The `declaration-empty-line-before` stylelint rule is being violated
at the `max-width: 60vw;` declaration. Review the stylelint configuration to
determine whether an empty line should be added before this declaration or
removed if one exists. Adjust the spacing before the `max-width: 60vw;`
declaration accordingly to comply with the configured rule, ensuring proper
formatting in the InstallerDetailsWidget styles file.
In
`@frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsPage.tsx`:
- Around line 316-343: Remove the hardcoded example LibraryItemAccordion
component block that contains the fabricated filename
"example-package-v2-really-long-package-name-to-see-what-happens-responsive-design.pkg"
and isActive={false} prop. This preview row was intended for design review
purposes only and should not be included in the production code before merging.
Delete the entire LibraryItemAccordion component instance along with all its
hardcoded props.
---
Nitpick comments:
In
`@frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tests.tsx`:
- Around line 140-145: Replace the `as never` type assertion on the `labels`
variable in the test case "renders a tooltip with the label list when hovering
the count badge" with a proper type assertion using `as ILabelSoftwareTitle[]`
instead. This will ensure TypeScript properly validates the object structure
matches the expected interface rather than bypassing type checking completely,
making the test more maintainable and catching shape mismatches.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 29fbd207-ff00-4cb5-a5f4-b644998ee2cf
📒 Files selected for processing (30)
.storybook/main.ts.storybook/preview.js.storybook/preview.scssfrontend/components/Icon/Icon.stories.tsxfrontend/components/TooltipTruncatedText/TooltipTruncatedText.tsxfrontend/components/TruncatedTextList/TruncatedTextList.stories.tsxfrontend/components/TruncatedTextList/TruncatedTextList.tsxfrontend/components/TruncatedTextList/_styles.scssfrontend/components/TruncatedTextList/index.tsfrontend/components/buttons/Button/Button.tsxfrontend/components/icons/Pin.tsxfrontend/components/icons/Tag.tsxfrontend/components/icons/index.tsfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.stories.tsxfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tests.tsxfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tsxfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordionList.stories.tsxfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordionList.tsxfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/_styles.scssfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/helpers.tests.tsfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/helpers.tsfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/index.tsfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/InstallerDetailsWidget/InstallerDetailsWidget.tests.tsxfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/InstallerDetailsWidget/InstallerDetailsWidget.tsxfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/InstallerDetailsWidget/_styles.scssfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/SoftwareInstallerCard.tsxfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsPage.tsxfrontend/test/storybook-utils.tsxfrontend/utilities/permissions/permissions.tests.tsfrontend/utilities/permissions/permissions.ts
💤 Files with no reviewable changes (2)
- frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/SoftwareInstallerCard.tsx
- frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/InstallerDetailsWidget/InstallerDetailsWidget.tests.tsx
cdcme
left a comment
There was a problem hiding this comment.
This looks good, barring some cleanup and adding tests before it hits main. 👍
7939f6a
into
feat/38504-auto-update-pin-rollback-fma
Note that SoftwareInstallerCard will be replaced with LibraryAccordionList once #47944 lands and this branch is rebased on top of it.
#48293) **Related issue:** Resolves #38504 **Constituent PRs (merged into this feature branch):** - #47682 — Fleet UI: APRF Software title details page Library/Inventory layout - #47808 — Extend update software installer API to support FMA version pinning - #47944 — Fleet UI: APRF library item accordion component - #48081 — Versions modal, multi-row Library, pinned state - #48098 — Add `pinned_version` to `edited_software` activity - #48123 — Auto-update FMA cron - #48144 — Download a newly-published FMA version when pinned to it # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. - [x] Timeouts are implemented and retries are limited to avoid infinite loops - [x] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes ## Testing - [x] Added/updated automated tests - [x] Where appropriate, [automated tests simulate multiple hosts and test for host isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing) (updates to one hosts's records do not affect another) - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Fleet-maintained app version pinning (Latest, exact, and major) via a new Versions modal. * Introduced premium auto-updates for maintained apps with pin-aware promotion and rollback-safe caching. * Added expandable library version rows and a Policies modal. * **Bug Fixes** * Improved pin handling, cache/manifest hydration, and safer update behavior on per-app failures and deduplication. * **UI/UX** * Refreshed the Software title details experience with new accordion/list patterns, redesigned details widget/tooltips, and updated installer presentation. * **Documentation** * Expanded Storybook component/page coverage and adjusted Storybook canvas padding. * **Tests** * Added/updated unit and integration tests for pinning, auto-update flows, and new modal/UI behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Bug fix (root cause): the "Latest" pin was rendered on every active library-accordion row regardless of installer type. `buildLibraryVersionRows` stamped `badgeState: "latest"` on the non-FMA fallback row and `SoftwareTitleDetailsPage` hard-coded it on the App Store branch. But the pin is FMA-only semantics (introduced with the FMA versioning feature in #47944) - for custom packages and App Store apps it had no meaning and no click handler, so it looked interactive (pointer cursor, hover state) without doing anything. Fix: drop `badgeState` on custom and App Store rows so no pin renders. LibraryItemAccordion: extracted the three duplicated badge blocks (latest / pinned / major-version) into one `renderStatusBadge` helper that emits a `<Button>` when `onBadgeClick` is wired and a static `<span>` (reusing the existing `--static` modifier) when it isn't. Covers the observer-viewing-FMA case, where the pin is info-only. Kept the label-count and "All hosts" fallback badges on non-FMA rows - labels can be scoped on any installer type.
Issue
Closes #47622
Description
LibraryItemAccordionpresentational component underfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/with a thinLibraryItemAccordionListwrapper. Collapsed row: filename, installer-type-aware version chip, badge slot. Expanded row: installed/pending/failed counts asCustomLinks built viagetPathWithQueryParams, labels block (Include any/Include all/Exclude any), SHA-256 hash + copy, download and trash buttons.badgeState?: "latest" | "pinned" | "majorVersion"with oneonBadgeClick. Type-level mutual exclusivity replaces three independent booleans + three callbacks.canEditSoftwareprop that mirrors backend WRITE on theSoftwareInstallerrego entity (admin | maintainer). Gates the label-count badge (button → static span), the labels-click handler, the inactive-row "Select Actions > Versions" hint tooltip, and the trash button (hidden entirely when false).canWriteSoftware(user, teamId)helper infrontend/utilities/permissions/permissions.tscentralizes the admin/maintainer-OR. Wired intoSoftwareTitleDetailsPageso the accordion stays role-agnostic.GitOpsModeTooltipWrapperwithentityType="software". Only FMA / App Store / Play Store rows are locked (custom packages stay deletable, mirroringSoftwareInstallerCard.SoftwareActionButtons). Tooltip text comes fromgetGitOpsModeTipContent(repoURL)— no inline copy duplication.helpers.tsexports a purederiveAccordionRowState({ rowVersion, activeVersion, pinnedVersion })for APRF: Wire up version pinning (Versions modal, multi-row Library, pinned-state display) #47623 to consume — keeps pin-vs-latest-vs-major branching in one tested place.InstallerDetailsWidgetcleanup: dropped the inline SHA chip + copy logic (accordion owns hash display now); added a unifieddisableTooltipsflag that suppresses every child tooltip on inactive rows so the row-level rollback tooltip is the only hover affordance.LibraryItemAccordion.stories.tsx(badge states, inactive, canEditSoftware off active + inactive, zero-install, no-labels) and 9 list-level stories inLibraryItemAccordionList.stories.tsx(per-OS multi-version, FMA / VPP / Play Store / in-house IPA) with reactive controls forlabelKind,labelCount, andbadgeState. New sharedwithFrame(width)decorator atfrontend/test/storybook-utils.tsx.Screenrecording
https://fleetdm.zoom.us/clips/share/z0aO8HmpS3CqYyqqWxhBIQ
2 fixes since the recording above:
Screen.Recording.2026-06-22.at.11.45.28.AM.mov
Screen.Recording.2026-06-22.at.11.45.52.AM.mov
Testing
LibraryItemAccordion.tests.tsx(nested by surface: collapsed header / expand+collapse / badges / inactive row / expanded panel — status counts / labels heading / hash copy / download / trash / no-handler safety), 8 inhelpers.tests.ts(every branch ofderiveAccordionRowStateincluding the inactive-row + pin edge case), 12 new inpermissions.tests.tscoveringcanWriteSoftwareacross global/team admin/maintainer/technician/observer/observer+.Summary by CodeRabbit
Release Notes
New Features
Improvements