Skip to content

Fleet UI: APRF library item accordion component - #47944

Merged
RachelElysia merged 14 commits into
feat/38504-auto-update-pin-rollback-fmafrom
47622-library-item-accordion
Jun 23, 2026
Merged

Fleet UI: APRF library item accordion component#47944
RachelElysia merged 14 commits into
feat/38504-auto-update-pin-rollback-fmafrom
47622-library-item-accordion

Conversation

@RachelElysia

@RachelElysia RachelElysia commented Jun 19, 2026

Copy link
Copy Markdown
Member

Issue

Closes #47622

Description

  • New LibraryItemAccordion presentational component under frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/ with a thin LibraryItemAccordionList wrapper. Collapsed row: filename, installer-type-aware version chip, badge slot. Expanded row: installed/pending/failed counts as CustomLinks built via getPathWithQueryParams, labels block (Include any / Include all / Exclude any), SHA-256 hash + copy, download and trash buttons.
  • Badge state is a discriminated union: badgeState?: "latest" | "pinned" | "majorVersion" with one onBadgeClick. Type-level mutual exclusivity replaces three independent booleans + three callbacks.
  • Role gating via a single required canEditSoftware prop that mirrors backend WRITE on the SoftwareInstaller rego 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).
  • New canWriteSoftware(user, teamId) helper in frontend/utilities/permissions/permissions.ts centralizes the admin/maintainer-OR. Wired into SoftwareTitleDetailsPage so the accordion stays role-agnostic.
  • GitOps mode handled internally via GitOpsModeTooltipWrapper with entityType="software". Only FMA / App Store / Play Store rows are locked (custom packages stay deletable, mirroring SoftwareInstallerCard.SoftwareActionButtons). Tooltip text comes from getGitOpsModeTipContent(repoURL) — no inline copy duplication.
  • helpers.ts exports a pure deriveAccordionRowState({ 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.
  • InstallerDetailsWidget cleanup: dropped the inline SHA chip + copy logic (accordion owns hash display now); added a unified disableTooltips flag that suppresses every child tooltip on inactive rows so the row-level rollback tooltip is the only hover affordance.
  • Storybook: 10 single-row stories in LibraryItemAccordion.stories.tsx (badge states, inactive, canEditSoftware off active + inactive, zero-install, no-labels) and 9 list-level stories in LibraryItemAccordionList.stories.tsx (per-OS multi-version, FMA / VPP / Play Store / in-house IPA) with reactive controls for labelKind, labelCount, and badgeState. New shared withFrame(width) decorator at frontend/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

  • Added/updated automated tests — 31 in 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 in helpers.tests.ts (every branch of deriveAccordionRowState including the inactive-row + pin edge case), 12 new in permissions.tests.ts covering canWriteSoftware across global/team admin/maintainer/technician/observer/observer+.
  • QA'd all new/changed functionality manually

Summary by CodeRabbit

Release Notes

  • New Features

    • Added TruncatedTextList component for displaying comma-separated item lists with smart truncation.
    • Added Pin and Tag icon components.
    • Added LibraryItemAccordion for expandable software version management with detailed status tracking.
    • Enhanced tooltip functionality with optional disable option.
  • Improvements

    • Refactored installer details widget layout and simplified UI presentation.
    • Enhanced Storybook with improved icon controls and expanded story documentation.
    • Updated component styling for better spacing and layout consistency.

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

codecov Bot commented Jun 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.68599% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.24%. Comparing base (295e0f8) to head (46cb5ee).
⚠️ Report is 159 commits behind head on main.

Files with missing lines Patch % Lines
...Page/LibraryItemAccordion/LibraryItemAccordion.tsx 93.54% 6 Missing ⚠️
...components/TruncatedTextList/TruncatedTextList.tsx 92.06% 5 Missing ⚠️
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     
Flag Coverage Δ
frontend 58.55% <94.68%> (+0.50%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@RachelElysia

Copy link
Copy Markdown
Member Author

@claude review

Comment on lines 385 to 393
return (
<>
{renderSoftwareSummaryCard(softwareTitle)}
{renderLibraryItemAccordionPreview(softwareTitle)}
{renderSoftwareInstallerCard(softwareTitle)}
{renderLibraryEditModal(softwareTitle)}
</>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 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 useSoftwareInstaller call): 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).

  1. renderSoftwareSummaryCard runs — produces the existing top card with the title's display name, icon, host counts.
  2. renderLibraryItemAccordionPreview runs (L212-345) — falls through the appStore branch (no app_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, real hash_sha256, real pkg.url for 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 copy pkg.status?.installed ?? 0 etc. from the real row, so the same numbers appear twice.
  3. renderSoftwareInstallerCard runs — produces the existing SoftwareInstallerCard which 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, but SoftwareInstallerCard still 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):

  1. Delete the renderLibraryItemAccordionPreview helper (L212-345) and its call site at L388.
  2. Delete the renderLibraryEditModal helper (L347-367) and its call site at L390.
  3. Delete the showLibraryEditModal state at L199 and the useSoftwareInstaller(softwareTitle ?? ...) call at L224.
  4. Drop the imports added solely for this preview: useSoftwareInstaller, ILabelSoftwareTitle, EditSoftwareModal, getDisplayedSoftwareName, and the LibraryItemAccordion + LibraryItemAccordionList + LibraryItemLabelKind imports.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is intended for the draft branch until we wire it up to the API

@RachelElysia
RachelElysia marked this pull request as ready for review June 22, 2026 16:55
@RachelElysia
RachelElysia requested a review from a team as a code owner June 22, 2026 16:55
Copilot AI review requested due to automatic review settings June 22, 2026 16:55

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Copilot AI left a comment

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.

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 InstallerDetailsWidget to 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.

Comment on lines +212 to +216
// 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) => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is intended for now so the reviewer can see what it looks like on the page

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

Comment thread frontend/components/TruncatedTextList/TruncatedTextList.tsx
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR introduces the LibraryItemAccordion presentational component for the Software Title Details page, which renders a collapsible row for a software installer version with badge states (latest, pinned, majorVersion), label/scope display, status count links, SHA-256 hash copy, download, and GitOps-gated delete actions. A LibraryItemAccordionList container wraps multiple accordion rows. The new TruncatedTextList component uses ResizeObserver to fit a string list into its container. InstallerDetailsWidget is refactored to remove SHA-256 display and add hideInstallerType/disableTooltips props. A canWriteSoftware permission helper is added. Pin and Tag SVG icons are registered in ICON_MAP. SoftwareTitleDetailsPage is updated to call useSoftwareInstaller and render the accordion preview alongside an EditSoftwareModal.

Possibly related issues

  • #47622 (APRF: Library item accordion component): This PR directly implements the LibraryItemAccordion component at frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/, covering all in-scope states (collapsed, expanded, badge variants, inactive, zero-install), Storybook entries, and Jest tests as specified in the issue's condition of satisfaction.
  • #47621: Both this PR and #47621 modify SoftwareTitleDetailsPage.tsx to add library accordion UI structure; this PR adds renderLibraryItemAccordionPreview and renderLibraryEditModal to the page.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Fleet UI: APRF library item accordion component' directly and specifically describes the main change: implementing a new UI component for displaying accordion-style library items.
Description check ✅ Passed The PR description comprehensively covers the implementation, including component structure, state management, role gating, testing coverage, and Storybook stories. It provides clear context despite not strictly following the template sections.
Linked Issues check ✅ Passed The PR fully satisfies the requirements from issue #47622: collapsed/expanded row rendering with badges, installation counts, labels, SHA-256 hash, download/trash buttons, pure presentation with no API calls, and extensive Storybook/Jest coverage.
Out of Scope Changes check ✅ Passed All changes align with issue #47622 scope. Icon components (Pin, Tag) support Storybook enhancements. InstallerDetailsWidget refactoring removes duplicate SHA logic. Permissions helper enables role gating. No unrelated out-of-scope changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 47622-library-item-accordion

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 6

🧹 Nitpick comments (1)
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tests.tsx (1)

140-145: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider using proper type assertion instead of as never.

The as never cast completely bypasses TypeScript's type checking. While this works for tests, using as ILabelSoftwareTitle[] (like makeLabels does 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

📥 Commits

Reviewing files that changed from the base of the PR and between d190d64 and 46cb5ee.

📒 Files selected for processing (30)
  • .storybook/main.ts
  • .storybook/preview.js
  • .storybook/preview.scss
  • frontend/components/Icon/Icon.stories.tsx
  • frontend/components/TooltipTruncatedText/TooltipTruncatedText.tsx
  • frontend/components/TruncatedTextList/TruncatedTextList.stories.tsx
  • frontend/components/TruncatedTextList/TruncatedTextList.tsx
  • frontend/components/TruncatedTextList/_styles.scss
  • frontend/components/TruncatedTextList/index.ts
  • frontend/components/buttons/Button/Button.tsx
  • frontend/components/icons/Pin.tsx
  • frontend/components/icons/Tag.tsx
  • frontend/components/icons/index.ts
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.stories.tsx
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tests.tsx
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tsx
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordionList.stories.tsx
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordionList.tsx
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/_styles.scss
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/helpers.tests.ts
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/helpers.ts
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/index.ts
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/InstallerDetailsWidget/InstallerDetailsWidget.tests.tsx
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/InstallerDetailsWidget/InstallerDetailsWidget.tsx
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/InstallerDetailsWidget/_styles.scss
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareInstallerCard/SoftwareInstallerCard.tsx
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsPage.tsx
  • frontend/test/storybook-utils.tsx
  • frontend/utilities/permissions/permissions.tests.ts
  • frontend/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

Comment thread .storybook/preview.scss
Comment thread frontend/components/TruncatedTextList/TruncatedTextList.tsx
Comment thread frontend/components/TruncatedTextList/TruncatedTextList.tsx
@RachelElysia
RachelElysia changed the base branch from main to feat/38504-auto-update-pin-rollback-fma June 22, 2026 17:09

@cdcme cdcme left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks good, barring some cleanup and adding tests before it hits main. 👍

@RachelElysia
RachelElysia merged commit 7939f6a into feat/38504-auto-update-pin-rollback-fma Jun 23, 2026
29 checks passed
@RachelElysia
RachelElysia deleted the 47622-library-item-accordion branch June 23, 2026 12:46
RachelElysia added a commit that referenced this pull request Jun 23, 2026
Note that SoftwareInstallerCard will be replaced with LibraryAccordionList
once #47944 lands and this branch is rebased on top of it.
georgekarrv added a commit that referenced this pull request Jun 26, 2026
#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 -->
RachelElysia added a commit that referenced this pull request Jul 7, 2026
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.
@coderabbitai coderabbitai Bot mentioned this pull request Jul 9, 2026
4 tasks
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.

APRF: Library item accordion component

3 participants