Skip to content

Fleet UI: Multi-package Library, Add/Edit/Delete modals (#48400)#48520

Draft
RachelElysia wants to merge 2 commits into
feat/28108-multiple-custom-packagesfrom
48400-multi-package-library-pr
Draft

Fleet UI: Multi-package Library, Add/Edit/Delete modals (#48400)#48520
RachelElysia wants to merge 2 commits into
feat/28108-multiple-custom-packagesfrom
48400-multi-package-library-pr

Conversation

@RachelElysia

Copy link
Copy Markdown
Member

Related issue: Resolves #48400

Description

Frontend implementation of the multi-package custom-package UI: the Library section iterates title.packages[], exposes per-installer Edit/Delete/Download, and adds an in-place Add Package modal with file-type restriction and a GitOps-mode variant. Coordinated state changes on the Software title details page move title-level Self-service / Auto install / Patch indicators down to per-row icons so they stay accurate when one package on a title differs from another.

Single source of truth at the page level: canActivateMultiplePackages flips three behaviors together — the "+ Add package" action, the per-row self-service / auto-install icons, and the summary card's chip-hide + pencil-Edit treatment. True for custom non-FMA non-iOS titles (Mac/Linux/Windows binary + script packages); false for FMA, VPP, Google Play, and iOS in-house .ipa, which stay on the single-package path.

Two commits:

  1. Foundation — types (packages[], installer_id?), API client extension, MSW handlers for the new endpoints, initial Library iteration.
  2. Modals + Library row indicatorsAddPackageModal (real in-place modal wrapping PackageForm), per-installer wiring through EditSoftwareModal / DeleteSoftwareModal, per-row clickable self-service / auto-install icons, page-level selectedInstallerId resolution, per-package PoliciesModal, pencil-icon Edit on the summary card with GitOpsModeTooltipWrapper, useBlockNavigation hook (deduplicates beforeunload across 3 files), pluralized "Custom package(s)" chip via stringUtils.pluralize, Latest badge tightened to FMA-only, InfoBanner icon prop wired up. Includes new component-level tests (AddPackageModal, EditSoftwareModal title swap, accordion icon row), helper tests (file-type restriction, MSW factories), and updated chip-render tests on the summary card. 411 SoftwarePage tests pass.

Verbatim copy from Figma page 2:130 throughout, including the deliberately preserved apostrophe typo in the GitOps banner.

Screen recording

Testing

  • yarn tsc --noEmit clean
  • npx eslint on touched files clean (one pre-existing defaultSoftware?: any warning in PackageForm.tsx:114 left as-is; out of scope)
  • yarn test --testPathPattern="SoftwarePage|useBlockNavigation" — 411/411 pass
  • Manual browser test via yarn start covering: multi-package render, +Add package flow, per-row edit/delete, file-type restriction, GitOps mode banner + disabled pencil Edit, 10-cap tooltip, "Edit package" modal title swap

Notes for reviewer

  • API backend (Multiple packages: packages[] API and add/edit/delete-package endpoints #48397) isn't merged yet — handlers stub installer_id and the multi-package POST/PATCH/DELETE shapes via MSW. Real wiring is a no-op until the server populates packages[]; the back-compat fallback (title.packages ?? [software_package].filter(Boolean)) keeps single-package titles rendering identically to pre-PR.
  • The installer_id field name is a guess pending Multiple packages: packages[] API and add/edit/delete-package endpoints #48397; comments call out the search-and-replace path when locked.
  • TODOs flagged in code that should be addressed before this merges to main:
    • Strip the #48396 / #48397 / #48400 issue refs sprinkled in comments (git blame is the audit trail)
    • Make installer_id required on ISoftwarePackage once #48397 ships
    • Drop the software_package back-compat fallback in renderLibrarySection once packages[] is always present

Out of scope for this PR

Foundation slice for the multi-package title-details page. Renders
title.packages[] when present and falls back to [software_package] so
back-compat single-package titles look identical to before. No new
modals yet — the AddPackageModal and per-installer Edit/Delete wiring
land in a follow-up slice.

- Types: optional installer_id on ISoftwarePackage; packages[] on
  ISoftwareTitle and ISoftwareTitleDetails. installer_id stays optional
  until #48397 ships and the server populates it everywhere.
- API client: addSoftwarePackage takes software_title_id (multi-package
  add); editSoftwarePackage and deleteSoftwareInstaller take an optional
  installer_id so they target one specific package.
- MSW: new frontend/test/handlers/multi-package-handlers.ts with a
  buildTitleWithPackages helper and handlers for GET (multi/empty), POST
  (happy + duplicate-hash + 10-limit + FMA-conflict + VPP-conflict),
  PATCH per-installer, and DELETE per-installer.
- Library section iterates packages instead of operating on a single
  software_package; description copy updated to Figma. App-store /
  custom-package branches stay mutually exclusive at the data layer.

TODOs flagged in code:
- Strip issue refs (#48396/#48397/#48400) from comments before merging
  to main.
- Make installer_id required once #48397 ships.
- Drop the software_package fallback in renderLibrarySection once
  packages[] is always present.
…48400)

Builds on the foundation slice with the per-package modal flows and the
coordinated UI states for multi-package custom titles. Hides title-level
status indicators (chips, page-level Edit) in favor of per-package
indicators on each Library accordion row so the surfaces stay accurate
when one package on a title differs from another.

Single source of truth at the page level: `canActivateMultiplePackages`
flips three behaviors together — the "+ Add package" action, the per-row
self-service / auto-install icons, and the summary card's chip-hide
and pencil-Edit treatment. True for custom non-FMA non-iOS titles
(Mac/Linux/Windows binary + script packages); false for FMA, VPP, Google
Play, and iOS in-house .ipa, which stay on the single-package path.

New components
- AddPackageModal: real in-place modal wrapping PackageForm. File-type
  chooser is restricted to the existing title's platform (helper at
  AddPackageModal/helpers.ts maps filename -> {accept, label}, with the
  .tar.gz workaround mirrored). Standard mode renders the first-added-wins
  banner; GitOps mode renders the SHA-256/YAML banner with the "YAML docs"
  link. Target defaults to Custom so labels are required.
- PackageForm: new multiPackageContext prop embeds the right banner under
  the file uploader, swaps the Save button copy, hides the deploy slider,
  and shows the target-label selector above Advanced options. Caught and
  fixed a pre-existing duplication of the submit button text across the
  tooltipped/non-tooltipped branches that let the copy drift.
- useBlockNavigation hook: extracts the `beforeunload` pattern that was
  duplicated across SoftwareCustomPackage, EditSoftwareModal, and the new
  AddPackageModal.

Per-package wiring
- selectedInstallerId state on the page resolves which package the Edit /
  Delete modal targets; row callbacks pass `pkg.installer_id`.
- EditSoftwareModal title swaps to "Edit package" via the new
  canActivateMultiplePackages prop; PATCH carries the installer id.
- DeleteSoftwareModal DELETE carries the installer id when set.
- Per-package PoliciesModal: clicking the auto-install icon navigates to
  the single linked policy or opens a modal scoped to that package's
  policies, distinct from the title-aggregate modal owned by the
  SoftwareSummaryCard.

Library accordion row
- Custom non-iOS rows hide the Latest badge and render clickable
  self-service / auto-install indicator icons. A renderRowActionIcon
  helper deduplicates the tooltip + optional button + static-fallback
  pattern.
- Latest badge tightened to FMA-only (`isFma && badgeState === "latest"`)
  so VPP/Play Store/iOS rows don't surface it either.
- ariaLabels: "Edit package" (matches the modal title that opens) for
  self-service; "View auto-install policies" / "View patch policy" for
  the auto-install icon.

Summary card
- Hides Self-service / Auto install / Patch chips when
  canActivateMultiplePackages — per-row icons replace them.
- Collapses the Actions dropdown to a single pencil-icon Edit button
  that opens the Edit Appearance modal directly; per-installer editing
  is on the accordion. The pencil button is wrapped in
  GitOpsModeTooltipWrapper so GitOps mode disables it with the standard
  "Managed by GitOps" tooltip.
- "Custom package(s)" chip pluralizes via `pluralize()` from stringUtils.

InfoBanner
- Wired up the existing `icon` prop (previously unused per its TODO) so
  banners can render a leading Icon. CSS gates the layout shift behind
  the existing `__icon` modifier so callers without the prop are
  unaffected.

Tests
- AddPackageModal component tests (13): title, banner copy verbatim,
  GitOps mode variant + YAML link, file-type restriction across .pkg
  /.deb/.msi/.tar.gz/.sh, Save vs Add software label, Custom radio
  preselected.
- EditSoftwareModal title swap (3 tests).
- LibraryItemAccordion custom-package row (4 tests): hidden by default,
  self-service icon fires onSelfServiceClick, auto-install icon fires
  onAutoInstallClick, patch-policy variant uses the patch aria-label.
- Updated SoftwareSummaryCard chip tests to use FMA mocks so they keep
  exercising the chip-render path; added a new test verifying chips are
  hidden for multi-package titles.
- New helper tests: getFileTypeRestriction (10), MSW factories (8).
- 411 SoftwarePage tests passing total (up from ~211 pre-PR).

Verbatim copy from Figma page 2:130 throughout (including the "it's"
apostrophe typo in the GitOps banner — preserved deliberately so design
and code stay in sync).

TODOs before merge to main:
- Strip the #48396/#48397/#48400 issue refs sprinkled in comments.
- Lock the `installer_id` field name once #48397 settles.
@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.30435% with 62 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.55%. Comparing base (b547f1a) to head (58e70e0).

Files with missing lines Patch % Lines
frontend/test/handlers/multi-package-handlers.ts 44.68% 26 Missing ⚠️
...tleDetailsPage/AddPackageModal/AddPackageModal.tsx 48.71% 20 Missing ⚠️
frontend/services/entities/software.ts 0.00% 6 Missing ⚠️
frontend/hooks/useBlockNavigation.ts 37.50% 5 Missing ⚠️
...ePage/components/forms/PackageForm/PackageForm.tsx 87.50% 3 Missing ⚠️
...lsPage/DeleteSoftwareModal/DeleteSoftwareModal.tsx 0.00% 1 Missing ⚠️
...Page/LibraryItemAccordion/LibraryItemAccordion.tsx 96.55% 1 Missing ⚠️
Additional details and impacted files
@@                           Coverage Diff                           @@
##           feat/28108-multiple-custom-packages   #48520      +/-   ##
=======================================================================
+ Coverage                                67.48%   67.55%   +0.06%     
=======================================================================
  Files                                     3676     3680       +4     
  Lines                                   233629   233792     +163     
  Branches                                 12261    12337      +76     
=======================================================================
+ Hits                                    157672   157931     +259     
+ Misses                                   61806    61709      -97     
- Partials                                 14151    14152       +1     
Flag Coverage Δ
frontend 59.42% <66.30%> (+0.46%) ⬆️

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.

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.

1 participant