Fleet UI: Move Add-entity buttons out of table headers - #50102
Conversation
Moves "Add profile", "Add asset", "Add script", "Add variable", "Add vital", and "Add certificate" out of table/list headers and SectionHeader details slots into a new `__tab-header` flex row that sits above the list, right of the section description — so the buttons align consistently across Controls pages. Configuration profiles now pushes its description + button into each of its two tabs (Profiles / Assets); the SectionHeader carries only the title. Global variables drops the row count. Empty `.table-container__header` collapses via `:has()` so removing the count/action/search doesn't leave a gap. Removes the unused ScriptListHeading component.
There was a problem hiding this comment.
Pull request overview
This PR standardizes Controls page layouts by moving “Add …” action buttons out of list/table headers and into a consistent flex “tab header” row alongside each section’s description, improving alignment across tabs and cards. It also prevents empty TableContainer headers from leaving visual gaps after removing counts/actions/search in some views.
Changes:
- Moved add-entity buttons (profiles, assets, scripts, variables, vitals, certificates) from
UploadListHeading/SectionHeaderheader areas into new__tab-headerrows above their respective lists. - Removed the unused
ScriptListHeadingcomponent and updated Script Library to render “Add script” in the new header row. - Updated
TableContainerstyles to fully hide.table-container__headerwhen it contains no results count, controls, or search bar.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/pages/ManageControlsPage/Variables/cards/GlobalVariables/GlobalVariables.tsx | Removes table count and relocates “Add variable” into a new tab-header row with the description. |
| frontend/pages/ManageControlsPage/Variables/cards/CustomHostVitalsTab/CustomHostVitalsTab.tsx | Relocates “Add vital” out of SectionHeader details into a tab-header row; aligns icon sizing and tooltip position. |
| frontend/pages/ManageControlsPage/Variables/_styles.scss | Adds shared flex tab-header styling for Variables cards. |
| frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/ScriptListHeading.tsx | Removes unused component. |
| frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/index.ts | Removes unused re-export. |
| frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/_styles.scss | Removes unused styling. |
| frontend/pages/ManageControlsPage/Scripts/cards/ScriptLibrary/ScriptLibrary.tsx | Moves “Add script” into a new tab-header row and drops UploadList heading component usage. |
| frontend/pages/ManageControlsPage/Scripts/cards/ScriptLibrary/_styles.scss | Adds tab-header flex styling for Script Library. |
| frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/ConfigurationProfiles.tsx | Moves description + “Add profile” into the Profiles tab header row; removes list heading component usage. |
| frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/AssetsTab/AssetsTab.tsx | Moves description + “Add asset” into a tab-header row; removes list heading component usage. |
| frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/AssetsTab/_styles.scss | Adds tab-header styling and applies vertical tab panel layout mixin. |
| frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/_styles.scss | Adds .profiles-tab layout and tab-header styling for the Profiles tab panel. |
| frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/Certificates.tsx | Moves description + “Add certificate” into a tab-header row and applies the intended premium/Android/custom-SCEP gating. |
| frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/_styles.scss | Switches to vertical-card-layout and adds tab-header styling. |
| frontend/components/TableContainer/_styles.scss | Collapses empty .table-container__header using :has() checks to avoid blank gaps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The "Add script" button now lives in the tab-header row above the list rather than inside the list header, so it stays mounted even when the list is empty and when the modal is closed. Two deep-link tests need to account for the extra occurrence: 2 → 3 when the modal is open (tab-header + modal title + modal submit), and 0 → 1 after the modal is dismissed.
Self-review — CTO-styleMust fix1. Double "Add" CTAs on empty states — six surfaces Pre-PR,
Design decision needed: keep the persistent tab-header CTA (drop the empty-state ones) or hide the tab-header button when the list is empty (mirror the pre-PR scoping). 2. Deep-link tests asserting Should fix3. Orphaned
4.
5. Duplicated Identical block copy-pasted across six files: &__tab-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: $pad-medium;
}Candidate for a shared mixin in Nit6.
7.
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #50102 +/- ##
==========================================
+ Coverage 68.05% 68.09% +0.04%
==========================================
Files 3929 3931 +2
Lines 250284 250289 +5
Branches 13238 13398 +160
==========================================
+ Hits 170324 170439 +115
+ Misses 64661 64550 -111
- Partials 15299 15300 +1
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:
|
- (#3) Deleted orphaned components/UploadListHeading/ folder — no external importers left after the tab-header refactor. - (#4) ConfigurationProfiles: hide the __tab-header (description + Add button) when !mdmEnabled so the "Create and upload configuration profiles..." copy no longer contradicts the "MDM must be turned on" EmptyState directly below. - (#5) Extracted the duplicated __tab-header block into a @mixin tab-header in styles/var/mixins.scss; six copies across five files now @include it. - (#6) Comment on .profiles-tab explaining why ConfigurationProfiles splits its card wrapper from its per-tab layout (two inner tabs mirroring .assets-tab) while Certificates / ScriptLibrary stay single-block (one-tab card). - (#7) Comment on <span className="controls"> in TableContainer.tsx flagging the load-bearing shape that _styles.scss's :has(.controls > *) collapse rule depends on.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/ConfigurationProfiles.tsx:312
- The profiles tab description (including the “Learn more” link) now only renders when
mdmEnabledis true. Previously it rendered even when MDM is disabled, and this change isn’t mentioned in the PR description. If MDM is off, users will lose that contextual/help link and the layout becomes inconsistent with the Assets tab (which always shows its description).
{mdmEnabled && (
<div className="profiles-tab__tab-header">
<PageDescription
variant="right-panel"
content={profilesDescription}
Tests each surface touched by this PR (tab-header row above the list with description + optional Add button): - Certificates.tests.tsx (new): 4 tests — description always renders; Add certificate hidden on Free, hidden without a custom SCEP CA, shown for premium + Android MDM + custom SCEP CA. - ConfigurationProfiles.tests.tsx (new): 3 tests — MDM enabled shows description + Add profile; MDM disabled keeps description but hides Add profile (EmptyState explains "MDM must be turned on"); technician sees the "View configuration profiles." copy with no Add profile button. - AssetsTab.tests.tsx: added a tab-header test above a populated list and adjusted the empty-state assertion to expect two "Add asset" buttons (empty-state CTA + persistent tab-header — matches the accepted double-CTA pattern from self-review item #1). - CustomHostVitalsTab.tests.tsx: added a tab-header describe block asserting the description + Add vital button render above the list. Also reverts the earlier suppress-when-MDM-off change on ConfigurationProfiles.tsx: the description now always renders (its "Learn more" link stays useful even when MDM is off) and the existing `showAddProfileButton = mdmEnabled && !isTechnician` gate is what hides the button. That matches the behavior the new tests assert.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
frontend/components/TableContainer/_styles.scss:91
- The new header-collapse selector checks for
.table-container__results-countelement presence, butTableContaineralways renders that wrapper wheneverrenderCountis provided—even ifrenderCount()returnsnull. This means tables that intentionally returnnullfor an empty count (e.g. LabelsTable) will still keep an empty header visible. Consider matching on.table-container__results-count > *(like you already do for.controls > *) so the header collapses when the count wrapper has no children.
// Hide the header entirely when it has no visible content (no
// results count, no filter/action controls, and no search bar).
&:not(:has(.table-container__results-count)):not(:has(.controls
> *)):not(:has(.table-container__search)) {
display: none;
WalkthroughManage page actions are moved from upload-list and section headers into dedicated tab-header rows for certificates, profiles, assets, scripts, variables, and custom host vitals. A shared Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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.
🧹 Nitpick comments (1)
frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/ConfigurationProfiles.tsx (1)
288-331: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffExtract the repeated tab-header action pattern into a shared component.
The same JSX shape —
PageDescription+ a conditionally-visible,GitOpsModeTooltipWrapper-wrapped secondary Button with a plusIcon, inside a__tab-headerwrapper — is copy-pasted across all three files below (and, per the PR description, extends to more surfaces in sibling layers of this stack). The SCSS side of this duplication was already consolidated into a sharedtab-headermixin; the JSX side is a natural follow-up.
frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/ConfigurationProfiles.tsx#L288-L331: extract thePageDescription+ conditional "Add profile"GitOpsModeTooltipWrapper/Button/Iconblock into a shared component (e.g.TabHeaderAction) parameterized by description content, button label,onClick, and ashowboolean.frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/Certificates.tsx#L248-L286: replace the equivalent "Add certificate" block with the same shared component.frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/AssetsTab/AssetsTab.tsx#L174-L199: replace the equivalent "Add asset" block with the same shared component.🤖 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/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/ConfigurationProfiles.tsx` around lines 288 - 331, The repeated tab-header JSX should be extracted into a shared TabHeaderAction component. Update ConfigurationProfiles.tsx (288-331), Certificates.tsx (248-286), and AssetsTab.tsx (174-199) to use it, parameterizing description content, button label, onClick, and show while preserving each existing GitOps wrapper, secondary button, plus icon, and tab-header layout.
🤖 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.
Nitpick comments:
In
`@frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/ConfigurationProfiles.tsx`:
- Around line 288-331: The repeated tab-header JSX should be extracted into a
shared TabHeaderAction component. Update ConfigurationProfiles.tsx (288-331),
Certificates.tsx (248-286), and AssetsTab.tsx (174-199) to use it,
parameterizing description content, button label, onClick, and show while
preserving each existing GitOps wrapper, secondary button, plus icon, and
tab-header layout.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 74edda79-cc8c-423b-a50f-27ce182880ee
📒 Files selected for processing (25)
frontend/components/TableContainer/TableContainer.tsxfrontend/components/TableContainer/_styles.scssfrontend/pages/ManageControlsPage/OSSettings/cards/Certificates/Certificates.tests.tsxfrontend/pages/ManageControlsPage/OSSettings/cards/Certificates/Certificates.tsxfrontend/pages/ManageControlsPage/OSSettings/cards/Certificates/_styles.scssfrontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/ConfigurationProfiles.tests.tsxfrontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/ConfigurationProfiles.tsxfrontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/_styles.scssfrontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/AssetsTab/AssetsTab.tests.tsxfrontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/AssetsTab/AssetsTab.tsxfrontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/AssetsTab/_styles.scssfrontend/pages/ManageControlsPage/Scripts/cards/ScriptLibrary/ScriptLibrary.tests.tsxfrontend/pages/ManageControlsPage/Scripts/cards/ScriptLibrary/ScriptLibrary.tsxfrontend/pages/ManageControlsPage/Scripts/cards/ScriptLibrary/_styles.scssfrontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/ScriptListHeading.tsxfrontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/_styles.scssfrontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/index.tsfrontend/pages/ManageControlsPage/Variables/_styles.scssfrontend/pages/ManageControlsPage/Variables/cards/CustomHostVitalsTab/CustomHostVitalsTab.tests.tsxfrontend/pages/ManageControlsPage/Variables/cards/CustomHostVitalsTab/CustomHostVitalsTab.tsxfrontend/pages/ManageControlsPage/Variables/cards/GlobalVariables/GlobalVariables.tsxfrontend/pages/ManageControlsPage/components/UploadListHeading/UploadListHeading.tsxfrontend/pages/ManageControlsPage/components/UploadListHeading/_styles.scssfrontend/pages/ManageControlsPage/components/UploadListHeading/index.tsfrontend/styles/var/mixins.scss
💤 Files with no reviewable changes (6)
- frontend/pages/ManageControlsPage/components/UploadListHeading/_styles.scss
- frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/index.ts
- frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/ScriptListHeading.tsx
- frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/_styles.scss
- frontend/pages/ManageControlsPage/components/UploadListHeading/index.ts
- frontend/pages/ManageControlsPage/components/UploadListHeading/UploadListHeading.tsx
Six Add buttons landed on main just ahead of this branch and still use the old <Icon>-child pattern. Migrate them to the icon prop for consistency with the rest of this branch's refactor.
Issue
Resolves #34374
Description
Moves "Add profile", "Add asset", "Add script", "Add variable", "Add vital", and "Add certificate" out of table headers (
UploadListHeading/SectionHeaderdetails) into a new__tab-headerflex row above the list, right of the section description — so the buttons align consistently across Controls pages.SectionHeadernow carries only the title. Description + Add button move into each of the two tabs (Profiles / Assets). When MDM is off, the description still renders (with its "Learn more" link) but the Add button hides via the existingshowAddProfileButton = mdmEnabled && !isTechniciangate — the description reads as informational above the "MDM must be turned on" EmptyState below.isPremiumTier && androidMdmEnabled && hasCustomScepCA..table-container__headercollapses via:has()when it has no count / controls / search — so removing count/action/search doesn't leave an empty gap. The<span className="controls">inTableContainer.tsxnow carries a comment flagging that its shape is load-bearing for the collapse selector.ScriptListHeadingand (post self-review)UploadListHeadingcomponents.&__tab-header { display: flex; justify-content: space-between; align-items: flex-start; gap: $pad-medium; }block into a shared@mixin tab-headerinstyles/var/mixins.scss; six copies across five files now@includeit.Screenrecording
Screen.Recording.2026-07-28.at.4.09.20.PM.mov
Testing
Summary by CodeRabbit