Fleet UI: New empty states - #43896
Conversation
f090e37 to
f9f00a5
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #43896 +/- ##
==========================================
- Coverage 66.78% 66.77% -0.01%
==========================================
Files 2627 2626 -1
Lines 211157 211169 +12
Branches 9422 9547 +125
==========================================
- Hits 141017 141007 -10
- Misses 57321 57343 +22
Partials 12819 12819
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@RachelElysia I tried to watch your video but I'm getting a black screen with audio only. Is the video working for you? |
|
@lukeheath !? Me neither 🫠 I'll re-record Monday, fix any merge conflicts and ping ya again |
b8f987e to
bfc9385
Compare
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
This PR normalizes Fleet’s non-inline UI empty states by introducing a new EmptyState component (with a “ghost table” backdrop) and swapping many existing EmptyTable/custom empty-state renderers to use it.
Changes:
- Added
frontend/components/EmptyState(component, styles, and Storybook stories). - Replaced
EmptyTableand various bespoke empty-state markup across pages/tables/modals withEmptyState. - Removed
GenericMsgWithNavButtonand updated call sites to renderEmptyState+ a button directly.
Reviewed changes
Copilot reviewed 101 out of 102 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
frontend/pages/queries/edit/components/QueryResults/QueryResults.tsx |
Replace “no results” message with EmptyState. |
frontend/pages/queries/details/components/QueryReport/QueryReport.tsx |
Replace table empty state with EmptyState. |
frontend/pages/queries/details/components/NoResults/NoResults.tsx |
Replace EmptyTable with EmptyState for query-results empty states. |
frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsx |
Use EmptyState for reports table empty state. |
frontend/pages/policies/edit/components/PolicyResultsTable/PolicyResultsTable.tsx |
Swap custom empty markup for EmptyState. |
frontend/pages/policies/edit/components/PolicyResults/PolicyResults.tsx |
Replace “no results” message with EmptyState. |
frontend/pages/policies/edit/components/PolicyErrorsTable/PolicyErrorsTable.tsx |
Swap custom empty markup for EmptyState. |
frontend/pages/policies/ManagePoliciesPage/components/PolicyRunScriptModal/PolicyRunScriptModal.tsx |
Use EmptyState when no scripts are available. |
frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx |
Use EmptyState for policies table empty state. |
frontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/_styles.scss |
Remove styles for old “no software” empty markup. |
frontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/InstallSoftwareModal.tsx |
Use EmptyState when no installable software titles exist. |
frontend/pages/packs/ManagePacksPage/components/PacksTable/PacksTable.tsx |
Use EmptyState for packs table empty state. |
frontend/pages/labels/ManageLabelsPage/LabelsTable/LabelsTable.tsx |
Use EmptyState for labels table empty state. |
frontend/pages/hosts/details/cards/Software/SelfService/components/SelfServiceTiles/SelfServiceTiles.tsx |
Use EmptyState for self-service search-empty state. |
frontend/pages/hosts/details/cards/Software/SelfService/components/SelfServiceTable/SelfServiceTable.tsx |
Use EmptyState for self-service table empty state. |
frontend/pages/hosts/details/cards/Software/SelfService/SelfServiceCard/SelfServiceCard.tsx |
Replace EmptyTable with EmptyState for error/empty UI. |
frontend/pages/hosts/details/cards/Software/SelfService/SelfServiceCard/SelfServiceCard.tests.tsx |
Update assertions for adjusted empty-state copy. |
frontend/pages/hosts/details/cards/Software/HostSoftwareTable/HostSoftwareTable.tsx |
Update empty-state component import to EmptyState. |
frontend/pages/hosts/details/cards/Queries/_styles.scss |
Remove styles used only by old custom empty markup. |
frontend/pages/hosts/details/cards/Queries/HostQueries.tsx |
Replace custom empty markup with EmptyState. |
frontend/pages/hosts/details/cards/Policies/HostPolicies.tsx |
Replace EmptyTable with EmptyState for unsupported/empty states. |
frontend/pages/hosts/details/cards/MunkiIssues/MunkiIssues.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/hosts/details/cards/LocalUserAccounts/LocalUserAccounts.tsx |
Replace EmptyTable with EmptyState in multiple states. |
frontend/pages/hosts/details/cards/HostSoftwareLibrary/HostSoftwareLibraryTable/HostSoftwareLibraryTable.tsx |
Replace EmptyTable with EmptyState for unsupported hosts. |
frontend/pages/hosts/details/cards/Activity/EmptyFeed/_styles.scss |
Remove bespoke empty-feed styles (now EmptyState). |
frontend/pages/hosts/details/cards/Activity/EmptyFeed/EmptyFeed.tsx |
Replace custom empty-feed markup with EmptyState. |
frontend/pages/hosts/details/HostQueryReport/HQRTable/HQRTable.tsx |
Replace EmptyTable empty states with EmptyState. |
frontend/pages/hosts/details/HostDetailsPage/modals/SelectQueryModal/_styles.scss |
Remove bespoke “no queries” styles (now EmptyState). |
frontend/pages/hosts/details/HostDetailsPage/modals/SelectQueryModal/SelectQueryModal.tsx |
Replace “no queries” blocks with EmptyState. |
frontend/pages/hosts/details/HostDetailsPage/modals/RunScriptModal/RunScriptModal.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx |
Replace EmptyTable unsupported-software state with EmptyState. |
frontend/pages/hosts/details/DeviceUserPage/components/SettingUpYourDevice/SetupStatusTable/SetupStatusTable.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/hosts/ManageHostsPage/components/RunScriptBatchModal/RunScriptBatchModal.tsx |
Replace EmptyTable with EmptyState and set variant. |
frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx |
Replace EmptyTable usage with EmptyState. |
frontend/pages/admin/UserManagementPage/components/UsersTable/UsersTable.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/admin/TeamManagementPage/components/EmptyTeamsTable.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/EmptyUsersTable.tsx |
Replace EmptyTable with EmptyState (both search/non-search). |
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/EndUserMigrationSection/EndUserMigrationSection.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage/_styles.scss |
Remove now-unused empty-tenant modifier selector. |
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage/WindowsAutomaticEnrollmentPage.tsx |
Replace card-wrapped empty message with EmptyState. |
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/VppPage.tsx |
Replace EmptyTable/GenericMsgWithNavButton with EmptyState. |
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/_styles.scss |
Remove bespoke “add ABM” empty styles. |
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/AppleBusinessManagerPage.tsx |
Replace GenericMsgWithNavButton and custom empty markup with EmptyState. |
frontend/pages/admin/IntegrationsPage/cards/Integrations/components/EmptyIntegrationsTable.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/admin/IntegrationsPage/cards/Integrations/Integrations.tsx |
Remove redundant Card wrapper around empty state. |
frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityCard/_styles.scss |
Remove bespoke add-CA empty styles. |
frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityCard/AddCertAuthorityCard.tsx |
Replace Card-based empty UI with EmptyState. |
frontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx |
Replace EmptyTable with EmptyState for vuln empty states. |
frontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/EmptyVulnerabilitiesTable/EmptyVulnerabilitiesTable.tsx |
Replace EmptyTable with EmptyState and adjust empty-state logic. |
frontend/pages/SoftwarePage/components/tables/OSKernelsTable/OSKernelsTable.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/SoftwarePage/components/tables/EmptySoftwareTable/EmptySoftwareTable.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareSummaryCard/TitleVersionsTable/TitleVersionsTable.tsx |
Replace card-wrapped EmptyTable with EmptyState. |
frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStoreVpp/_styles.scss |
Remove bespoke VPP empty-state styles. |
frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStoreVpp/SoftwareAppStoreVpp.tsx |
Replace VPP empty-state markup with EmptyState. |
frontend/pages/ManageControlsPage/Variables/Variables.tsx |
Replace EmptyTable with EmptyState for variables empty state. |
frontend/pages/ManageControlsPage/Variables/Variables.tests.tsx |
Loosen assertion to match updated empty-state copy. |
frontend/pages/ManageControlsPage/SetupExperience/cards/Users/Users.tsx |
Minor import/format cleanup. |
frontend/pages/ManageControlsPage/SetupExperience/cards/SetupAssistant/SetupAssistant.tsx |
Replace GenericMsgWithNavButton with EmptyState for prerequisites. |
frontend/pages/ManageControlsPage/SetupExperience/cards/RunScript/RunScript.tsx |
Replace GenericMsgWithNavButton with EmptyState for prerequisites. |
frontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/components/InstallSoftwareTable/InstallSoftwareTable.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/components/InstallSoftwareForm/InstallSoftwareForm.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/InstallSoftware.tsx |
Replace GenericMsgWithNavButton with EmptyState for prerequisites. |
frontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/components/BootstrapPackageTable/BootstrapPackageTable.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/BootstrapPackage.tsx |
Replace GenericMsgWithNavButton with EmptyState for prerequisites. |
frontend/pages/ManageControlsPage/Scripts/cards/ScriptLibrary/_styles.scss |
Remove bespoke empty-scripts styling. |
frontend/pages/ManageControlsPage/Scripts/cards/ScriptLibrary/ScriptLibrary.tsx |
Use EmptyState for empty script library state. |
frontend/pages/ManageControlsPage/Scripts/cards/ScriptBatchProgress/_styles.scss |
Remove bespoke empty styles. |
frontend/pages/ManageControlsPage/Scripts/cards/ScriptBatchProgress/ScriptBatchProgress.tsx |
Use EmptyState for batch progress tab empty states. |
frontend/pages/ManageControlsPage/Scripts/ScriptBatchDetailsPage/_styles.scss |
Remove bespoke empty styles. |
frontend/pages/ManageControlsPage/Scripts/ScriptBatchDetailsPage/ScriptBatchDetailsPage.tsx |
Use EmptyState for batch details tab empty states. |
frontend/pages/ManageControlsPage/OSUpdates/components/OSVersionsEmptyState/_styles.scss |
Remove now-unused empty-table margin styles. |
frontend/pages/ManageControlsPage/OSUpdates/components/OSVersionsEmptyState/OSVersionsEmptyState.tsx |
Replace card-wrapped EmptyTable with EmptyState. |
frontend/pages/ManageControlsPage/OSUpdates/OSUpdates.tsx |
Replace GenericMsgWithNavButton with EmptyState for prerequisites. |
frontend/pages/ManageControlsPage/OSSettings/cards/Passwords/Passwords.tsx |
Replace GenericMsgWithNavButton with EmptyState for prerequisites. |
frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/components/DiskEncryptionTable/DiskEncryptionTable.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/ConfigProfileStatusTable/ConfigProfileStatusTable.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/ConfigurationProfiles.tsx |
Replace GenericMsgWithNavButton with EmptyState for prerequisites. |
frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/Certificates.tsx |
Replace GenericMsgWithNavButton with EmptyState for prerequisites. |
frontend/pages/DashboardPage/cards/OperatingSystems/OSTable.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/DashboardPage/cards/Munki/Munki.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/DashboardPage/cards/MDM/MDM.tsx |
Replace EmptyTable with EmptyState. |
frontend/pages/DashboardPage/cards/ActivityFeed/ActivityFeed.tsx |
Replace EmptyTable with EmptyState and set variant. |
frontend/components/queries/PackQueriesTable/PackQueriesTable.tsx |
Replace bespoke “no queries” block and search-empty state with EmptyState. |
frontend/components/queries/LiveResults/AwaitingResults/AwaitingResults.tsx |
Replace EmptyTable with EmptyState. |
frontend/components/forms/fields/SelectTargetsDropdown/SelectTargetsMenu/_styles.scss |
Remove bespoke “no hosts” styles. |
frontend/components/forms/fields/SelectTargetsDropdown/SelectTargetsMenu/SelectTargetsMenu.jsx |
Replace bespoke “no hosts” copy block with EmptyState. |
frontend/components/TargetsInput/_styles.scss |
Remove bespoke empty-search styles. |
frontend/components/TargetsInput/TargetsInput.tsx |
Replace bespoke empty-search markup with EmptyState. |
frontend/components/TargetLabelSelector/TargetLabelSelector.tsx |
Add note explaining why EmptyState isn’t used. |
frontend/components/TabNav/_styles.scss |
Remove styles for deprecated .no-results-message markup. |
frontend/components/LiveQuery/SelectTargets.tsx |
Add note explaining why EmptyState isn’t used. |
frontend/components/GenericMsgWithNavButton/index.ts |
Remove deprecated component export. |
frontend/components/GenericMsgWithNavButton/_styles.scss |
Remove deprecated component styles. |
frontend/components/GenericMsgWithNavButton/GenericMsgWithNavButton.tsx |
Remove deprecated component implementation. |
frontend/components/GenericMsgWithNavButton/GenericMsgWithNavButton.tests.tsx |
Remove deprecated component tests. |
frontend/components/EnrollSecrets/EnrollSecretModal/EnrollSecretModal.tsx |
Replace EmptyTable with EmptyState for enroll secrets empty state. |
frontend/components/EmptyState/index.ts |
New barrel export for EmptyState. |
frontend/components/EmptyState/_styles.scss |
New EmptyState ghost-table styling and variants. |
frontend/components/EmptyState/EmptyState.tsx |
New EmptyState component implementation. |
frontend/components/EmptyState/EmptyState.stories.tsx |
New Storybook stories covering variants/widths/buttons. |
changes/35483-empty-states |
Changelog entry for updated empty states. |
Comments suppressed due to low confidence (1)
frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx:1767
EmptyStatedoesn't define agraphicNameprop, but the object passed here includesgraphicName. This will fail type-checking and break the build. Either removegraphicNamefrom the props passed toEmptyState(and stop basing this onIEmptyTableProps), or addgraphicNametoIEmptyStatePropsif you need it.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <EmptyState | ||
| variant="list" | ||
| header="Additional configuration required" | ||
| info="Supported on macOS. To customize, first turn on automatic enrollment." | ||
| path={PATHS.ADMIN_INTEGRATIONS_MDM} | ||
| buttonText="Turn on" | ||
| router={router} | ||
| primaryButton={ | ||
| <Button onClick={() => router.push(PATHS.ADMIN_INTEGRATIONS_MDM)}> | ||
| Turn on | ||
| </Button> | ||
| } | ||
| /> |
There was a problem hiding this comment.
This renders a "Turn on" button unconditionally. Since the Setup Experience area isn't role-restricted (only premium-gated), non-global admins may see a CTA that navigates to an admin-only page. If you want to preserve the previous behavior from GenericMsgWithNavButton, gate primaryButton on isGlobalAdmin (via AppContext) or otherwise ensure only authorized roles see the button.
There was a problem hiding this comment.
ClaudeCode:
Whoever is generating these review comments doesn't seem aware of the route-level and tab-level guards. You can push back on both with the same reasoning:
- Route guard: AuthAnyMaintainerAdminTechnicianRoutes blocks observers/observer+
- Tab filter: technicians are excluded from Setup Experience tabs
- Only admins and maintainers reach these components, and both can access the MDM integrations page
| <EmptyState | ||
| variant="form" | ||
| header="Additional configuration required" | ||
| info="To customize, first turn on automatic enrollment." | ||
| buttonText="Turn on" | ||
| path={PATHS.ADMIN_INTEGRATIONS_MDM} | ||
| router={router} | ||
| primaryButton={ | ||
| <Button onClick={() => router.push(PATHS.ADMIN_INTEGRATIONS_MDM)}> | ||
| Turn on | ||
| </Button> | ||
| } | ||
| /> |
There was a problem hiding this comment.
This "Turn on" CTA is now always rendered. Previously, the removed GenericMsgWithNavButton only showed the navigation button to global admins; SetupExperience itself doesn't restrict access by role (it only checks isPremiumTier). Consider restoring the role-gating (e.g., primaryButton={isGlobalAdmin ? ... : undefined}) to avoid showing a non-functional admin-only action to non-global admins.
There was a problem hiding this comment.
There are two layers of protection:
- Route level (AuthAnyMaintainerAdminTechnicianRoutes) — blocks observers and observer+ from reaching /controls at all
- Tab level (lines 120-122 in ManageControlsPage) — further blocks technicians from the Setup Experience tab
Only admins and maintainers (global or team) can reach the "Turn on" button. Both roles have permission to navigate to the MDM integrations page it links to. So the claim that there's a missing role-gate
is not accurate — the button is already properly restricted.
| <EmptyState | ||
| variant="form" | ||
| header="Additional configuration required" | ||
| info="Supported on macOS. To customize, first turn on automatic enrollment." | ||
| buttonText="Turn on" | ||
| path={PATHS.ADMIN_INTEGRATIONS_MDM} | ||
| router={router} | ||
| primaryButton={ | ||
| <Button onClick={() => router.push(PATHS.ADMIN_INTEGRATIONS_MDM)}> | ||
| Turn on | ||
| </Button> | ||
| } |
There was a problem hiding this comment.
The empty-state "Turn on" button is now always shown. The old GenericMsgWithNavButton component gated this CTA to global admins, and SetupExperience doesn't appear to restrict access by role. To avoid presenting an action that non-global admins can't complete, consider making primaryButton conditional on isGlobalAdmin (AppContext) or handling authorization at the route level.
There was a problem hiding this comment.
Same thing — same wrong premise, same answer. All three of these (SetupAssistant, RunScript, BootstrapPackage) are cards within Setup Experience, so they all inherit the same route-level and tab-level
protection. The review comment is copy-paste of the same incorrect analysis.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
WalkthroughIntroduces a new reusable EmptyState React component (TSX + SCSS) with Storybook stories and a new IEmptyStateProps interface (removing Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (11)
frontend/pages/hosts/details/cards/Software/HostSoftwareTable/HostSoftwareTable.tsx (1)
33-64:⚠️ Potential issue | 🟡 MinorRemove unused imports.
EmptyState,CustomLink, andSUPPORT_LINKare imported on Lines 33–35 but never used in this file.EmptySoftwareTable(whichEmptyComponentcorrectly delegates to) already handles those dependencies internally. Remove the three unused imports to reduce bundle noise and avoid linter warnings.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/hosts/details/cards/Software/HostSoftwareTable/HostSoftwareTable.tsx` around lines 33 - 64, The file imports unused symbols EmptyState, CustomLink, and SUPPORT_LINK; remove those three unused imports from the top of HostSoftwareTable.tsx to eliminate lint warnings and reduce bundle noise—locate the import block that currently declares EmptyState, CustomLink, and SUPPORT_LINK and delete those identifiers so only the actually used imports (e.g., EmptySoftwareTable, PLATFORM_DISPLAY_NAMES, isVulnUnsupportedPlatform if present) remain; verify EmptyComponent and EmptySoftwareTable still compile and run after the removal.frontend/pages/DashboardPage/cards/OperatingSystems/OSTable.tsx (1)
19-26:⚠️ Potential issue | 🟡 MinorHeader fallback never triggers — produces "No undefined operating systems detected".
In the changed JSX:
header={`No${ ` ${PLATFORM_DISPLAY_NAMES[platform]}` || "" } operating systems detected`}The inner template literal
` ${PLATFORM_DISPLAY_NAMES[platform]}`is always a non-empty string (it has a leading space), so the|| ""short-circuit is dead code. WhenselectedPlatformis not inPLATFORM_DISPLAY_NAMES(e.g.,"all"), the rendered header becomes"No undefined operating systems detected"instead of"No operating systems detected".This pre-dates the PR but sits inside the changed
<EmptyState>expression — worth fixing while you’re here.🛠 Proposed fix
-const EmptyOS = (platform: PlatformValueOptions): JSX.Element => ( - <EmptyState - className={`${baseClass}__os-empty-table`} - header={`No${ - ` ${PLATFORM_DISPLAY_NAMES[platform]}` || "" - } operating systems detected`} - info="This report is updated every hour to protect the performance of your - devices." - /> -); +const EmptyOS = (platform: PlatformValueOptions): JSX.Element => { + const platformName = PLATFORM_DISPLAY_NAMES[platform]; + return ( + <EmptyState + className={`${baseClass}__os-empty-table`} + header={`No${platformName ? ` ${platformName}` : ""} operating systems detected`} + info="This report is updated every hour to protect the performance of your devices." + /> + ); +};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/DashboardPage/cards/OperatingSystems/OSTable.tsx` around lines 19 - 26, The header builds a string that can render "undefined" because the inner template literal (` ${PLATFORM_DISPLAY_NAMES[platform]}`) always returns a non-empty string; update the logic so the platform display name is only appended when it exists — e.g., compute a displayName variable from PLATFORM_DISPLAY_NAMES[platform] and use displayName ? ` No ${displayName} operating systems detected` (or construct `No${displayName ? ` ${displayName}` : ""} operating systems detected`) in the EmptyState header; target the header expression near the EmptyState JSX and the PLATFORM_DISPLAY_NAMES/platform usage to implement this conditional.frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/components/DiskEncryptionTable/DiskEncryptionTable.tsx (1)
92-98:⚠️ Potential issue | 🟡 MinorTypo in user-facing empty-state copy.
"Expecting to status data?"is missing a verb. Likely intended"Expecting to see status data?", matching the wording used by other empty-state messages in this PR (e.g., the newEmptyUsersTable's "Expecting to see users?").✏️ Proposed fix
emptyComponent={() => ( <EmptyState header="No disk encryption status" - info="Expecting to status data? Try again in a few seconds as the system - catches up." + info="Expecting to see status data? Try again in a few seconds as the system catches up." /> )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/components/DiskEncryptionTable/DiskEncryptionTable.tsx` around lines 92 - 98, In DiskEncryptionTable update the user-facing EmptyState copy: replace the malformed string "Expecting to status data? Try again in a few seconds as the system catches up." with "Expecting to see status data? Try again in a few seconds as the system catches up." so the EmptyState (used in DiskEncryptionTable) matches the phrasing used elsewhere (e.g., EmptyUsersTable).frontend/pages/packs/ManagePacksPage/components/PacksTable/PacksTable.tsx (1)
4-4:⚠️ Potential issue | 🔴 CriticalRemove
IEmptyTablePropsimport and refactoremptyState()to matchEmptyState's actual prop signature.
EmptyStateusesIEmptyStateProps, which does not includegraphicName. The code currently types theemptyState()result asIEmptyTableProps(the old interface for theEmptyTablecomponent) and passesgraphicName={emptyState().graphicName}on line 135, whichEmptyStatesilently ignores. Additionally, callingemptyState()four times per render (lines 135–138) unnecessarily reconstructs theprimaryButton<Button>JSX each time—call it once instead.🔧 Proposed fix
-import { IEmptyTableProps } from "interfaces/empty_table"; import Button from "components/buttons/Button"; @@ - // TODO: useCallback search string - const emptyState = () => { - const emptyPacks: IEmptyTableProps = { - graphicName: "empty-packs", - header: "You don't have any packs", - info: - "Query packs allow you to schedule recurring queries for your hosts.", - primaryButton: ( - <Button - className={`${baseClass}__create-button`} - onClick={onCreatePackClick} - > - Create new pack - </Button> - ), - }; - if (searchString) { - delete emptyPacks.graphicName; - emptyPacks.header = "No packs match the current search criteria"; - emptyPacks.info = - "Expecting to see packs? Try again in a few seconds as the system catches up."; - delete emptyPacks.primaryButton; - } - return emptyPacks; - }; + const emptyState = () => { + if (searchString) { + return { + header: "No packs match the current search criteria", + info: + "Expecting to see packs? Try again in a few seconds as the system catches up.", + primaryButton: undefined, + }; + } + return { + header: "You don't have any packs", + info: "Query packs allow you to schedule recurring queries for your hosts.", + primaryButton: ( + <Button + className={`${baseClass}__create-button`} + onClick={onCreatePackClick} + > + Create new pack + </Button> + ), + }; + }; @@ - emptyComponent={() => ( - <EmptyState - graphicName={emptyState().graphicName} - header={emptyState().header} - info={emptyState().info} - primaryButton={emptyState().primaryButton} - /> - )} + emptyComponent={() => { + const empty = emptyState(); + return ( + <EmptyState + header={empty.header} + info={empty.info} + primaryButton={empty.primaryButton} + /> + ); + }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/packs/ManagePacksPage/components/PacksTable/PacksTable.tsx` at line 4, Remove the unused import IEmptyTableProps and update emptyState() to return the actual EmptyState prop shape (IEmptyStateProps) instead of IEmptyTableProps; stop passing the non-existent graphicName prop to <EmptyState> and instead pass only the props defined on IEmptyStateProps. Also build the primaryButton JSX once (create const { primaryButton, ...emptyProps } = emptyState(); or cache the result in a variable) and reuse that primaryButton when rendering rather than calling emptyState() multiple times; update all references (emptyState(), primaryButton, EmptyState) accordingly.frontend/components/queries/PackQueriesTable/PackQueriesTable.tsx (1)
86-128:⚠️ Potential issue | 🟡 MinorInconsistent terminology: "query" vs. "report" within the same component.
This change introduces mixed labels:
- Empty-search header (line 88):
"No queries match your search criteria"- Toolbar action button (lines 94–95):
name: "add query",buttonText: "Add query"- No-data empty state (line 115):
"Your pack has no reports"- No-data CTA (line 123):
"Add report"Per
frontend/pages/hosts/details/cards/Queries/HostQueries.tsx:71-76,141-150, the established pattern is "report" for scheduled queries in the UI. Either align all four labels to "report" (and consider the modal title"Add query"infrontend/pages/packs/EditPackPage/components/PackQueryEditorModal/PackQueryEditorModal.tsx:186), or revert the empty-state CTA back to "Add query" so a single page doesn't switch terminology mid-flow.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/queries/PackQueriesTable/PackQueriesTable.tsx` around lines 86 - 128, The component uses mixed terminology ("query" vs "report") across PackQueriesTable: normalize labels to "report" to match the rest of the app (e.g., HostQueries) — update the EmptyState header, actionButton.name, actionButton.buttonText, and primaryButton text in PackQueriesTable to use "report" instead of "query"; also review PackQueryEditorModal (Add query modal title) and change its title to "Add report" if you want full consistency, or alternatively revert the PackQueriesTable CTA back to "Add query" so the page uses a single term consistently.frontend/pages/admin/TeamManagementPage/components/EmptyTeamsTable.tsx (1)
42-57:⚠️ Potential issue | 🟠 Major
EmptyStatedoes not support graphics—add illustration back or confirm removal is intentional.The previous
EmptyTablecomponent rendered a graphic viagraphicName="empty-teams", butEmptyStatehas no graphic, illustration, or image prop. The component currently renders only ghost skeletons, header text, and buttons. Confirm this visual change aligns with design, or add a graphic prop toEmptyStateto restore the illustration.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/admin/TeamManagementPage/components/EmptyTeamsTable.tsx` around lines 42 - 57, EmptyState in EmptyTeamsTable.tsx replaced the previous EmptyTable that used graphicName="empty-teams", so the illustration disappeared; either restore the illustration by adding a graphic/illustration prop to EmptyState and pass graphicName="empty-teams" (or a new prop name) from EmptyTeamsTable, or intentionally remove the graphic and update the component naming/comment to reflect that change. Locate EmptyTeamsTable.tsx (where EmptyState is rendered) and the EmptyState component implementation, add a graphic prop (e.g., graphicName or illustration) to EmptyState, wire it through its renderer to display the same "empty-teams" asset, and update primaryButton usage if needed; alternatively, if removal is intentional, add a short comment in EmptyTeamsTable.tsx clarifying the design decision and remove any leftover references to graphicName/EmptyTable.frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx (1)
1733-1770:⚠️ Potential issue | 🟠 MajorUse JSX syntax for
EmptyStateand remove the unsupportedgraphicNameprop.Two issues in this block:
Calling
EmptyState({...})as a plain function is not idiomatic React. Use JSX syntax:Suggested change
- {EmptyState({ - graphicName: emptyState().graphicName, - header: emptyState().header, - info: emptyState().info, - additionalInfo: emptyState().additionalInfo, - primaryButton: emptyState().primaryButton, - })} + <EmptyState + header={emptyState().header} + info={emptyState().info} + additionalInfo={emptyState().additionalInfo} + primaryButton={emptyState().primaryButton} + />
graphicNameis not supported byEmptyState. The component's prop interface does not includegraphicName, so passing it has no effect. Remove it from the call. (Note:EmptyStaterenders a ghost table skeleton instead of a graphic illustration, which is intentional per the redesign.)
additionalInfois never set byemptyState(), so the prop always passesundefinedand has no visible effect. Remove it unless it should be populated with content.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx` around lines 1733 - 1770, The current block calls EmptyState as a plain function and passes an unsupported graphicName prop (and an unused additionalInfo), causing non-idiomatic React and dead props; fix by calling EmptyState with JSX instead of EmptyState(...), compute const state = emptyState() once (referencing emptyState, maybeEmptyHosts, includesFilterQueryParam, canEnrollHosts, toggleAddHostsModal) and return <EmptyState header={state.header} info={state.info} primaryButton={state.primaryButton} />; remove graphicName and additionalInfo from the props unless you intentionally add content for additionalInfo in emptyState().frontend/pages/ManageControlsPage/Scripts/ScriptBatchDetailsPage/ScriptBatchDetailsPage.tsx (1)
52-59:⚠️ Potential issue | 🟡 MinorMinor copy issues in
EMPTY_STATE_DETAILS.
- Line 54: trailing space at the end of
"Hosts with error results appear here. "will surface in the renderedinfotext.- Line 58: copy uses British spelling
"cancelled"while the status key (and the tab label below) is the American"canceled". Worth aligning for consistency.✏️ Proposed fix
ran: "Hosts with successful script results appear here.", - errored: "Hosts with error results appear here. ", + errored: "Hosts with error results appear here.", pending: "Compatible hosts that haven't run the script appear here.", incompatible: "Targeted hosts with incompatible operating systems appear here.", - canceled: "Hosts where this script run was cancelled appear here.", + canceled: "Hosts where this script run was canceled appear here.",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/ManageControlsPage/Scripts/ScriptBatchDetailsPage/ScriptBatchDetailsPage.tsx` around lines 52 - 59, EMPTY_STATE_DETAILS has minor copy issues: remove the trailing space from the errored value and align spelling for the canceled key/value; update the errored entry string (no trailing space) and change the "cancelled" text to American spelling "canceled" so the value for the canceled key in the EMPTY_STATE_DETAILS record (type ScriptBatchHostStatus) matches the status key and tab label.frontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/components/BootstrapPackageTable/BootstrapPackageTable.tsx (1)
63-65:⚠️ Potential issue | 🟡 MinorTypo in
infocopy.
"Expecting to status data?"is missing a verb — should read"Expecting to see status data?"to match the phrasing used elsewhere (e.g.,TargetsInput.tsx:"Expecting to see hosts? ...").✏️ Proposed fix
<EmptyState header="No bootstrap package status" - info="Expecting to status data? Try again in a few seconds as the system - catches up." + info="Expecting to see status data? Try again in a few seconds as the system catches up." />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/components/BootstrapPackageTable/BootstrapPackageTable.tsx` around lines 63 - 65, Fix the typo in the JSX info prop inside the BootstrapPackageTable component: replace the incorrect string "Expecting to status data? Try again in a few seconds as the system catches up." with the corrected copy "Expecting to see status data? Try again in a few seconds as the system catches up." so the info text matches phrasing used elsewhere (look for the info prop in BootstrapPackageTable).frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx (2)
83-85:⚠️ Potential issue | 🟡 MinorVerify empty-
inforendering parity.When
!canAddOrDeletePolicies,infois set to""(empty string).EmptyTableandEmptyStatemay treat truthiness differently — e.g.,EmptyStatecould still render an empty<p>/spacing for""whereasEmptyTableskipped it. Worth a quick visual check that the read-only empty state still looks right (likely better to setinfo: undefined/delete emptyState.info).🔧 Suggested adjustment
if (!canAddOrDeletePolicies) { - emptyState.info = ""; + delete emptyState.info; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx` around lines 83 - 85, The empty-state info is being set to an empty string when !canAddOrDeletePolicies which may render differently between EmptyTable and EmptyState; in PoliciesTable.tsx, update the logic around emptyState (the emptyState object modified when evaluating canAddOrDeletePolicies) to remove the info property or set it to undefined instead of "" so the read-only empty state renders consistently (verify by checking EmptyTable and EmptyState rendering afterwards).
65-70:⚠️ Potential issue | 🟡 MinorRe-type
emptyStateto match the component it's passed to.
emptyStateis typed asIEmptyTablePropsbut consumed byEmptyState, which expectsIEmptyStateProps. AlthoughgraphicNameis being passed toEmptyState(line 150), that component does not accept it in its props interface—it will be silently ignored. This type mismatch is misleading and obscures the fact thatgraphicNamehas no effect. Change the type annotation toIEmptyStatePropsto ensure consistency between the type label and what the component actually accepts.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx` around lines 65 - 70, The emptyState constant is currently typed as IEmptyTableProps but is passed into the EmptyState component which expects IEmptyStateProps and does not accept graphicName; change the type annotation of emptyState from IEmptyTableProps to IEmptyStateProps, remove or move any unsupported props (e.g., graphicName) so they align with EmptyState's prop shape, and ensure the constant's keys match the IEmptyStateProps interface (update the emptyState declaration and any usages of graphicName accordingly).
🧹 Nitpick comments (17)
frontend/pages/ManageControlsPage/Variables/Variables.tests.tsx (1)
58-58: Consider asserting exact empty-state text instead of a loose regex.
/No custom variables/iis broader than necessary and would also match text like "These are no custom variables removed." Since the newEmptyStateshould render a deterministic header, prefer an exact string (or at minimum an anchored regex) so casing/wording regressions are caught.Suggested change
- expect(screen.getByText(/No custom variables/i)).toBeInTheDocument(); + expect(screen.getByText("No custom variables")).toBeInTheDocument();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/ManageControlsPage/Variables/Variables.tests.tsx` at line 58, The test currently uses a loose regex expect(screen.getByText(/No custom variables/i)).toBeInTheDocument(); which may match unintended text; change it to assert the exact empty-state header—either use a string matcher expect(screen.getByText('No custom variables')).toBeInTheDocument(); or an anchored regex expect(screen.getByText(/^No custom variables$/i)).toBeInTheDocument(); so the assertion in Variables.tests.tsx targets the deterministic EmptyState header precisely.changes/35483-empty-states (1)
1-1: Add trailing newline at EOF.The file lacks a trailing newline, per the AI summary. POSIX convention recommends a final newline; many tools/linters flag its absence.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@changes/35483-empty-states` at line 1, The file changes/35483-empty-states is missing a trailing newline at EOF; open that file and add a single newline character at the end so the file ends with a blank line (POSIX-compliant), save the file, and recommit the change to satisfy linters and conventions.frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityCard/_styles.scss (1)
1-2: Delete the now-empty stylesheet.The file contains only a comment and is not imported anywhere. Remove
frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityCard/_styles.scssto keep the codebase clean.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityCard/_styles.scss` around lines 1 - 2, The stylesheet _styles.scss for AddCertAuthorityCard is empty (only a comment) and unused; delete the file and remove any lingering imports referencing AddCertAuthorityCard/_styles.scss (or imports in the AddCertAuthorityCard component) so there are no broken references.frontend/pages/policies/edit/components/PolicyErrorsTable/PolicyErrorsTable.tsx (1)
46-46: Consider addinginfocopy for parity with the design system.Most other migrated empty states in this PR pair
headerwithinfotext. With only a header here, the ghost-table backdrop will look quite sparse and gives users no guidance on what "No hosts are online" implies. If the Figma design includes secondary copy for this case, please thread it through theinfoprop; otherwise feel free to disregard.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/policies/edit/components/PolicyErrorsTable/PolicyErrorsTable.tsx` at line 46, The empty state in PolicyErrorsTable's emptyComponent currently only passes a header to EmptyState (<EmptyState header="No hosts are online" />); add an info prop to match the design system and other migrated empty states (e.g., <EmptyState header="No hosts are online" info="...secondary guidance..." />) — update the emptyComponent in PolicyErrorsTable.tsx to include appropriate secondary copy (use the Figma text if available or a short explanatory sentence) so the ghost-table backdrop isn't sparse.frontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/components/InstallSoftwareForm/InstallSoftwareForm.tsx (1)
235-250: Rename className to match current component and remove unused class reference.The
className={${baseClass}__empty-table}is indeed a leftover from when this renderedEmptyTable. The corresponding SCSS rule (install-software-form__empty-table) does not exist in_styles.scss, making this class a no-op. Rename to__empty-stateto align with the current component and improve code clarity.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/components/InstallSoftwareForm/InstallSoftwareForm.tsx` around lines 235 - 250, The EmptyState rendered by renderEmptyState uses a stale class name `${baseClass}__empty-table`; update it to `${baseClass}__empty-state` to match the current component and existing SCSS, i.e. in InstallSoftwareForm change the className prop on the EmptyState element from `${baseClass}__empty-table` to `${baseClass}__empty-state`; ensure no other references to `__empty-table` remain in this component.frontend/pages/SoftwarePage/components/tables/EmptySoftwareTable/EmptySoftwareTable.tsx (1)
4-4: Stale type: switch fromIEmptyTablePropstoIEmptyStateProps.
getEmptySoftwareInfonow feedsEmptyState, which doesn't supportgraphicName/additionalInfotable-only fields. UsingIEmptyTablePropsas the return type is misleading and could mask future bugs (e.g., someone addinggraphicNamehere expecting it to render).♻️ Proposed refactor
-import { IEmptyTableProps } from "interfaces/empty_table"; +import { IEmptyStateProps } from "components/EmptyState/EmptyState"; @@ - const getEmptySoftwareInfo = (): IEmptyTableProps => { + const getEmptySoftwareInfo = (): Pick<IEmptyStateProps, "header" | "info"> => {Also applies to: 58-58
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/SoftwarePage/components/tables/EmptySoftwareTable/EmptySoftwareTable.tsx` at line 4, The return type used in EmptySoftwareTable/getEmptySoftwareInfo is stale: replace IEmptyTableProps with IEmptyStateProps (update the import and any type annotations for getEmptySoftwareInfo and its usages) so the function returns only fields supported by EmptyState; also remove or stop returning table-only fields like graphicName and additionalInfo from getEmptySoftwareInfo to avoid misleading callers and runtime mismatches with the EmptyState component.frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsx (1)
180-200: DeadgraphicNamelogic —EmptyStateignores it.
emptyParamsis typed asIEmptyTablePropsand seedsgraphicName: "empty-queries", butEmptyState(perfrontend/components/EmptyState/EmptyState.tsx) does not acceptgraphicName. The prop is silently dropped when spread on Line 297, and thedelete emptyParams.graphicNameat Line 198 has no effect. This is misleading and will confuse future maintainers expecting a graphic to render.♻️ Proposed cleanup
- const emptyParams: IEmptyTableProps = { - graphicName: "empty-queries", - header: "You don't have any reports", - }; + const emptyParams: IEmptyStateProps = { + header: "You don't have any reports", + }; @@ if (searchQuery || curTargetedPlatformFilter !== "all") { - delete emptyParams.graphicName; emptyParams.header = "No matching reports"; emptyParams.info = "No reports match the current filters."; }Remove the now-unused
IEmptyTablePropsimport and importIEmptyStatePropsfrom theEmptyStatemodule instead.Also applies to: 297-297
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsx` around lines 180 - 200, The emptyParams object in QueriesTable is typed as IEmptyTableProps and seeds graphicName, but EmptyState ignores graphicName; update the import and typing to use IEmptyStateProps (replace IEmptyTableProps with IEmptyStateProps), remove the graphicName property initialization and the subsequent delete emptyParams.graphicName call, and adjust the emptyParams builds (the header/info mutations and conditional branches around currentTeamId, searchQuery, and curTargetedPlatformFilter) to only set properties that EmptyState actually consumes before the object is spread into <EmptyState /> in the QueriesTable component.frontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/EmptyVulnerabilitiesTable/EmptyVulnerabilitiesTable.tsx (1)
23-55: DeadgraphicNameentries —EmptyStateignores them.Every entry in
emptyStateDetailsplusdefaultEmptyStatestill carriesgraphicName, and the records are typed asPartial<IEmptyTableProps>/IEmptyTableProps. After migrating toEmptyState(which has nographicNameprop), these values are silently dropped on Line 107 (<EmptyState {...emptyState} />). This is dead configuration that will mislead readers/maintainers.♻️ Proposed cleanup
-import { IEmptyTableProps } from "interfaces/empty_table"; +import { IEmptyStateProps } from "components/EmptyState/EmptyState"; @@ -const emptyStateDetails: Record< - IVulnerabilitiesEmptyStateReason, - Partial<IEmptyTableProps> -> = { +const emptyStateDetails: Record< + IVulnerabilitiesEmptyStateReason, + Partial<IEmptyStateProps> +> = { "no-vulns-detected": { - graphicName: "empty-search-question", header: "No vulnerabilities detected", info: "Expecting to see vulnerabilities? Check back later.", }, "no-matching-items": { - graphicName: "empty-search-question", header: "No items match the current search criteria", info: "Expecting to see vulnerabilities? Check back later.", }, "invalid-cve": { - graphicName: "empty-search-exclamation", header: "That vulnerability (CVE) is not valid", info: 'Try updating your search to use CVE format: "CVE-YYYY-<4 or more digits>"', }, "unknown-cve": { - graphicName: "empty-search-question", header: "This is not a known CVE", info: "None of Fleet's vulnerability sources are aware of this CVE.", additionalInfo: <LearnMoreLink />, }, "known-vuln": { - graphicName: "empty-search-check", header: "This is a known vulnerability (CVE), but it wasn't detected on any hosts", additionalInfo: <LearnMoreLink />, }, }; @@ - const defaultEmptyState: IEmptyTableProps = { - graphicName: "empty-search-question", + const defaultEmptyState: IEmptyStateProps = { header: "No items match the current search criteria", info: "Expecting to see vulnerabilities? Check back later.", };Also applies to: 83-91
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/EmptyVulnerabilitiesTable/EmptyVulnerabilitiesTable.tsx` around lines 23 - 55, The emptyStateDetails and defaultEmptyState objects include a dead graphicName property that EmptyState ignores; remove all graphicName entries and update the types so callers can't accidentally include it (e.g., change the type of emptyStateDetails/defaultEmptyState from Partial<IEmptyTableProps> / IEmptyTableProps to Partial<Omit<IEmptyTableProps, "graphicName">> or define a new type without graphicName), and ensure the <EmptyState {...emptyState} /> usage and any tests/fixtures are updated to match the new type.frontend/pages/hosts/details/cards/MunkiIssues/MunkiIssues.tsx (1)
40-56: Deduplicate the empty-state copy.The same
header/infoprops are rendered for both the "TableContainer empty" and "no munkiIssues" branches. Hoist into a shared element (or render theEmptyStateonce whenmunkiIssues?.lengthis falsy and letTableContainerhandle the with-data empty case) to avoid drift if the copy changes later.♻️ Suggested refactor
- {munkiIssues?.length ? ( + {(() => { + const emptyState = ( + <EmptyState + header="No Munki issues detected" + info="The last time Munki ran on this host, no issues were reported." + /> + ); + return munkiIssues?.length ? ( <div className={deviceType || ""}> <TableContainer columnConfigs={tableHeaders} data={tableMunkiIssues || []} isLoading={isLoading} defaultSortHeader="name" defaultSortDirection="asc" resultsTitle="issue" - emptyComponent={() => ( - <EmptyState - header="No Munki issues detected" - info="The last time Munki ran on this host, no issues were reported." - /> - )} + emptyComponent={() => emptyState} showMarkAllPages={false} isAllPagesSelected={false} isClientSidePagination /> </div> - ) : ( - <EmptyState - header="No Munki issues detected" - info="The last time Munki ran on this host, no issues were reported." - /> - )} + ) : ( + emptyState + ); + })()}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/hosts/details/cards/MunkiIssues/MunkiIssues.tsx` around lines 40 - 56, The EmptyState JSX is duplicated in MunkiIssues.tsx for both the TableContainer empty case and the fallback when munkiIssues is falsy; refactor by hoisting the EmptyState into a single shared element: compute a single const (e.g. emptyMunki = <EmptyState header="No Munki issues detected" info="The last time Munki ran on this host, no issues were reported." />) or render EmptyState once when munkiIssues?.length is falsy and pass only the data-driven props into TableContainer, replacing the two duplicated JSX blocks so future copy changes are made in one place; update references to munkiIssues and TableContainer accordingly.frontend/pages/hosts/details/cards/Policies/HostPolicies.tsx (1)
100-125: Minor: drop unnecessary fragments around plain-text headers.
header={<>Policies are not supported for this host</>}can simply beheader="Policies are not supported for this host"on lines 101 and 116. The fragment is only needed where the value is a composite expression (e.g., the line 130-135 block).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/hosts/details/cards/Policies/HostPolicies.tsx` around lines 100 - 125, The EmptyState headers in HostPolicies.tsx currently wrap plain text in fragments (header={<>Policies are not supported for this host</>}); replace those fragment-wrapped values with simple string props (header="Policies are not supported for this host") for both occurrences (the iOS branch and the Android branch) so the header prop receives a plain string instead of a JSX fragment; keep the existing JSX fragments only for composite header/info values like the other block that uses CustomLink.frontend/pages/hosts/details/HostDetailsPage/modals/RunScriptModal/RunScriptModal.tsx (1)
121-124: Considerwidth="small"for the modal context.Per
IEmptyStateProps's own JSDoc —"default" renders 3 ghost columns (page-level), "small" renders 2 (modal-level)— this empty state is rendered insideModal, so passingwidth="small"would better match the documented intent and the modal's narrower layout.✏️ Proposed change
<EmptyState + width="small" header="No scripts available for this host" info="Expecting to see scripts? Close this modal and try again." />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/hosts/details/HostDetailsPage/modals/RunScriptModal/RunScriptModal.tsx` around lines 121 - 124, The EmptyState used in RunScriptModal is rendered inside a modal but currently relies on the default width; update the EmptyState component invocation (the EmptyState element in RunScriptModal) to pass width="small" per IEmptyStateProps so it uses the 2-column modal layout instead of the 3-column page layout.frontend/pages/ManageControlsPage/OSUpdates/components/OSVersionsEmptyState/OSVersionsEmptyState.tsx (1)
5-5: UnusedbaseClassconstant.
baseClassis no longer referenced after dropping the prior wrapper/className usage. Consider removing it to avoid dead code.♻️ Proposed cleanup
import EmptyState from "components/EmptyState"; -const baseClass = "os-versions-empty-state"; - const OSVersionsEmptyState = () => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/ManageControlsPage/OSUpdates/components/OSVersionsEmptyState/OSVersionsEmptyState.tsx` at line 5, Remove the dead constant by deleting the unused `baseClass` declaration (const baseClass = "os-versions-empty-state") from OSVersionsEmptyState.tsx; if the intent was to keep a BEM/class reference, instead reintroduce its use in the component's wrapper/className, otherwise simply remove the `baseClass` symbol to eliminate the unused variable.frontend/pages/hosts/details/cards/Activity/EmptyFeed/_styles.scss (1)
1-2: Delete the_styles.scssfile.It contains only a comment and is not imported by any component. Removing it will clean up unused files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/hosts/details/cards/Activity/EmptyFeed/_styles.scss` around lines 1 - 2, Remove the unused stylesheet file named _styles.scss (it only contains a comment and is not imported anywhere); delete the file from the repository and ensure no references/imports remain (verify related components like the EmptyState/Activity feed rely on its styles instead of this file).frontend/components/EnrollSecrets/EnrollSecretModal/EnrollSecretModal.tsx (1)
70-131: Optional: extract the duplicated "Add secret" button.The exact same
GitOpsModeTooltipWrapper+Button(with identical props, classes, icon, andonClick={addNewSecretClick}) is rendered both in the non-empty branch (Lines 71–86) and as theprimaryButtonof the empty state (Lines 115–130). Extracting to a single local helper/variable would remove ~15 lines of duplication and prevent future drift between the two call sites.♻️ Sketch of the extraction
+ const addSecretButton = ( + <GitOpsModeTooltipWrapper + entityType="secrets" + position="right" + tipOffset={8} + renderChildren={(disableChildren) => ( + <Button + disabled={disableChildren} + onClick={addNewSecretClick} + className={`${baseClass}__add-secret-btn`} + variant="brand-inverse-icon" + iconStroke + > + Add secret <Icon name="plus" color="core-fleet-green" /> + </Button> + )} + /> + );…and pass
addSecretButtonboth inside the header<div>and asprimaryButton={addSecretButton}.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/EnrollSecrets/EnrollSecretModal/EnrollSecretModal.tsx` around lines 70 - 131, Extract the duplicated "Add secret" JSX into a single local variable (e.g., const addSecretButton) inside EnrollSecretModal: move the GitOpsModeTooltipWrapper + Button (the instance using addNewSecretClick, baseClass + "__add-secret-btn", variant="brand-inverse-icon", iconStroke, and Icon name="plus") into that variable and then replace both occurrences (the header div and EmptyState primaryButton) with addSecretButton so both render the same shared element.frontend/pages/hosts/details/cards/Activity/EmptyFeed/EmptyFeed.tsx (1)
11-20: Optional: this thin wrapper may now be redundant.
EmptyFeedis now a near-passthrough toEmptyState(just renamingtitle/message→header/info). Consider inlining<EmptyState variant="list" header={...} info={...} />at the call sites and removing this file/component to reduce indirection. Not blocking.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/hosts/details/cards/Activity/EmptyFeed/EmptyFeed.tsx` around lines 11 - 20, The EmptyFeed component is a thin passthrough that only maps props (title→header, message→info) to EmptyState; remove the indirection by replacing usages of <EmptyFeed title=... message=... className=... /> with <EmptyState variant="list" header={title} info={message} className={className} /> at call sites and then delete the EmptyFeed component and its IEmptyFeedProps type if unused elsewhere (search for EmptyFeed and IEmptyFeedProps to update imports and remove the file).frontend/components/EmptyState/_styles.scss (1)
187-192: Avoid!importanton__ghost-inputbackground.
!importantis rarely needed for decorative skeleton primitives that aren't visible to assistive tech and aren't expected to be themed externally. If this is here to override a globalinput/buttonreset, prefer increasing specificity (e.g.,.empty-state__ghost-input { ... }already has class specificity, so the override is likely unnecessary) or document the override target inline.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/EmptyState/_styles.scss` around lines 187 - 192, Remove the unnecessary !important on the .__ghost-input background; update the .__ghost-input rule (the selector for the ghost input block) to rely on its current class specificity or, if you truly need to override a global reset, increase specificity (e.g., use the full component selector like .empty-state__ghost-input) or add an inline comment documenting the exact reset you're overriding so the intent is clear—then remove the !important declaration from the background-color property in the __ghost-input rule.frontend/components/EmptyState/EmptyState.stories.tsx (1)
15-19: Uselabelsconfiguration to customize the "undefined" option label.Storybook's select control renders
undefinedas the literal string "undefined", which is a confusing label for the default variant. Use thelabelsoption to provide a friendlier display name while preserving the actualundefinedvalue passed to the component.♻️ Proposed refactor
variant: { control: "select", options: [undefined, "list", "header-list", "form"], + labels: { + undefined: "Default", + }, },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/EmptyState/EmptyState.stories.tsx` around lines 15 - 19, The story's argType for the variant control currently shows the raw "undefined" string; update the variant argType (the control for variant in EmptyState.stories.tsx) to include a labels mapping that maps the undefined option to a friendly label (e.g., "Default" or "Auto") while keeping its value as undefined in the options array, so the select displays the friendly label but still passes undefined to the component. Locate the variant argType and add a labels object keyed by the option values (including undefined) to provide the custom display text.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 980bc68f-5590-4b45-8bde-ee553f4f576d
📒 Files selected for processing (102)
changes/35483-empty-statesfrontend/components/EmptyState/EmptyState.stories.tsxfrontend/components/EmptyState/EmptyState.tsxfrontend/components/EmptyState/_styles.scssfrontend/components/EmptyState/index.tsfrontend/components/EnrollSecrets/EnrollSecretModal/EnrollSecretModal.tsxfrontend/components/GenericMsgWithNavButton/GenericMsgWithNavButton.tests.tsxfrontend/components/GenericMsgWithNavButton/GenericMsgWithNavButton.tsxfrontend/components/GenericMsgWithNavButton/_styles.scssfrontend/components/GenericMsgWithNavButton/index.tsfrontend/components/LiveQuery/SelectTargets.tsxfrontend/components/TabNav/_styles.scssfrontend/components/TargetLabelSelector/TargetLabelSelector.tsxfrontend/components/TargetsInput/TargetsInput.tsxfrontend/components/TargetsInput/_styles.scssfrontend/components/forms/fields/SelectTargetsDropdown/SelectTargetsMenu/SelectTargetsMenu.jsxfrontend/components/forms/fields/SelectTargetsDropdown/SelectTargetsMenu/_styles.scssfrontend/components/queries/LiveResults/AwaitingResults/AwaitingResults.tsxfrontend/components/queries/PackQueriesTable/PackQueriesTable.tsxfrontend/pages/DashboardPage/cards/ActivityFeed/ActivityFeed.tsxfrontend/pages/DashboardPage/cards/MDM/MDM.tsxfrontend/pages/DashboardPage/cards/Munki/Munki.tsxfrontend/pages/DashboardPage/cards/OperatingSystems/OSTable.tsxfrontend/pages/ManageControlsPage/OSSettings/cards/Certificates/Certificates.tsxfrontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/ConfigurationProfiles.tsxfrontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/ConfigProfileStatusTable/ConfigProfileStatusTable.tsxfrontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/components/DiskEncryptionTable/DiskEncryptionTable.tsxfrontend/pages/ManageControlsPage/OSSettings/cards/Passwords/Passwords.tsxfrontend/pages/ManageControlsPage/OSUpdates/OSUpdates.tsxfrontend/pages/ManageControlsPage/OSUpdates/components/OSVersionsEmptyState/OSVersionsEmptyState.tsxfrontend/pages/ManageControlsPage/OSUpdates/components/OSVersionsEmptyState/_styles.scssfrontend/pages/ManageControlsPage/Scripts/ScriptBatchDetailsPage/ScriptBatchDetailsPage.tsxfrontend/pages/ManageControlsPage/Scripts/ScriptBatchDetailsPage/_styles.scssfrontend/pages/ManageControlsPage/Scripts/cards/ScriptBatchProgress/ScriptBatchProgress.tsxfrontend/pages/ManageControlsPage/Scripts/cards/ScriptBatchProgress/_styles.scssfrontend/pages/ManageControlsPage/Scripts/cards/ScriptLibrary/ScriptLibrary.tsxfrontend/pages/ManageControlsPage/Scripts/cards/ScriptLibrary/_styles.scssfrontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/BootstrapPackage.tsxfrontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/components/BootstrapPackageTable/BootstrapPackageTable.tsxfrontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/InstallSoftware.tsxfrontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/components/InstallSoftwareForm/InstallSoftwareForm.tsxfrontend/pages/ManageControlsPage/SetupExperience/cards/InstallSoftware/components/InstallSoftwareTable/InstallSoftwareTable.tsxfrontend/pages/ManageControlsPage/SetupExperience/cards/RunScript/RunScript.tsxfrontend/pages/ManageControlsPage/SetupExperience/cards/SetupAssistant/SetupAssistant.tsxfrontend/pages/ManageControlsPage/SetupExperience/cards/Users/Users.tsxfrontend/pages/ManageControlsPage/Variables/Variables.tests.tsxfrontend/pages/ManageControlsPage/Variables/Variables.tsxfrontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStoreVpp/SoftwareAppStoreVpp.tsxfrontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStoreVpp/_styles.scssfrontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppsTable/FleetMaintainedAppsTable.tsxfrontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareSummaryCard/TitleVersionsTable/TitleVersionsTable.tsxfrontend/pages/SoftwarePage/components/tables/EmptySoftwareTable/EmptySoftwareTable.tsxfrontend/pages/SoftwarePage/components/tables/OSKernelsTable/OSKernelsTable.tsxfrontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/EmptyVulnerabilitiesTable/EmptyVulnerabilitiesTable.tsxfrontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsxfrontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityCard/AddCertAuthorityCard.tsxfrontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityCard/_styles.scssfrontend/pages/admin/IntegrationsPage/cards/Integrations/Integrations.tsxfrontend/pages/admin/IntegrationsPage/cards/Integrations/components/EmptyIntegrationsTable.tsxfrontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/AppleBusinessManagerPage.tsxfrontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/_styles.scssfrontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/VppPage.tsxfrontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage/WindowsAutomaticEnrollmentPage.tsxfrontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage/_styles.scssfrontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/EndUserMigrationSection/EndUserMigrationSection.tsxfrontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/EmptyUsersTable.tsxfrontend/pages/admin/TeamManagementPage/components/EmptyTeamsTable.tsxfrontend/pages/admin/UserManagementPage/components/UsersTable/UsersTable.tsxfrontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsxfrontend/pages/hosts/ManageHostsPage/components/RunScriptBatchModal/RunScriptBatchModal.tsxfrontend/pages/hosts/details/DeviceUserPage/components/SettingUpYourDevice/SetupStatusTable/SetupStatusTable.tsxfrontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsxfrontend/pages/hosts/details/HostDetailsPage/modals/RunScriptModal/RunScriptModal.tsxfrontend/pages/hosts/details/HostDetailsPage/modals/SelectQueryModal/SelectQueryModal.tsxfrontend/pages/hosts/details/HostDetailsPage/modals/SelectQueryModal/_styles.scssfrontend/pages/hosts/details/HostQueryReport/HQRTable/HQRTable.tsxfrontend/pages/hosts/details/cards/Activity/EmptyFeed/EmptyFeed.tsxfrontend/pages/hosts/details/cards/Activity/EmptyFeed/_styles.scssfrontend/pages/hosts/details/cards/HostSoftwareLibrary/HostSoftwareLibraryTable/HostSoftwareLibraryTable.tsxfrontend/pages/hosts/details/cards/LocalUserAccounts/LocalUserAccounts.tsxfrontend/pages/hosts/details/cards/MunkiIssues/MunkiIssues.tsxfrontend/pages/hosts/details/cards/Policies/HostPolicies.tsxfrontend/pages/hosts/details/cards/Queries/HostQueries.tsxfrontend/pages/hosts/details/cards/Queries/_styles.scssfrontend/pages/hosts/details/cards/Software/HostSoftwareTable/HostSoftwareTable.tsxfrontend/pages/hosts/details/cards/Software/SelfService/SelfServiceCard/SelfServiceCard.tests.tsxfrontend/pages/hosts/details/cards/Software/SelfService/SelfServiceCard/SelfServiceCard.tsxfrontend/pages/hosts/details/cards/Software/SelfService/components/SelfServiceTable/SelfServiceTable.tsxfrontend/pages/hosts/details/cards/Software/SelfService/components/SelfServiceTiles/SelfServiceTiles.tsxfrontend/pages/labels/ManageLabelsPage/LabelsTable/LabelsTable.tsxfrontend/pages/packs/ManagePacksPage/components/PacksTable/PacksTable.tsxfrontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/InstallSoftwareModal.tsxfrontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/_styles.scssfrontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsxfrontend/pages/policies/ManagePoliciesPage/components/PolicyRunScriptModal/PolicyRunScriptModal.tsxfrontend/pages/policies/edit/components/PolicyErrorsTable/PolicyErrorsTable.tsxfrontend/pages/policies/edit/components/PolicyResults/PolicyResults.tsxfrontend/pages/policies/edit/components/PolicyResultsTable/PolicyResultsTable.tsxfrontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsxfrontend/pages/queries/details/components/NoResults/NoResults.tsxfrontend/pages/queries/details/components/QueryReport/QueryReport.tsxfrontend/pages/queries/edit/components/QueryResults/QueryResults.tsx
💤 Files with no reviewable changes (18)
- frontend/pages/ManageControlsPage/SetupExperience/cards/Users/Users.tsx
- frontend/components/GenericMsgWithNavButton/_styles.scss
- frontend/pages/ManageControlsPage/Scripts/cards/ScriptBatchProgress/_styles.scss
- frontend/pages/ManageControlsPage/OSUpdates/components/OSVersionsEmptyState/_styles.scss
- frontend/pages/hosts/details/cards/Queries/_styles.scss
- frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/_styles.scss
- frontend/components/TabNav/_styles.scss
- frontend/pages/policies/ManagePoliciesPage/components/InstallSoftwareModal/_styles.scss
- frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage/_styles.scss
- frontend/components/forms/fields/SelectTargetsDropdown/SelectTargetsMenu/_styles.scss
- frontend/pages/hosts/details/HostDetailsPage/modals/SelectQueryModal/_styles.scss
- frontend/components/GenericMsgWithNavButton/GenericMsgWithNavButton.tsx
- frontend/pages/ManageControlsPage/Scripts/ScriptBatchDetailsPage/_styles.scss
- frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAppStore/SoftwareAppStoreVpp/_styles.scss
- frontend/components/GenericMsgWithNavButton/index.ts
- frontend/components/GenericMsgWithNavButton/GenericMsgWithNavButton.tests.tsx
- frontend/pages/ManageControlsPage/Scripts/cards/ScriptLibrary/_styles.scss
- frontend/components/TargetsInput/_styles.scss
| if (isError) | ||
| return ( | ||
| <EmptyTable | ||
| header="Error loading software." | ||
| {...(isMobileView && { variant: "mobile" })} | ||
| <EmptyState | ||
| header="Error loading software" | ||
| {...(isMobileView && { variant: "list" })} | ||
| /> | ||
| ); |
There was a problem hiding this comment.
Verify error UX: skeleton ghost rows for an error state may look like loading.
EmptyState renders skeleton/ghost columns (or a ghost list when variant="list") as visual filler, which is well-suited to "no data yet" states but can be misleading for an error state — users may assume the page is still loading. Consider:
- Using a dedicated error component (e.g.,
DataError, as used in the siblingCertificates.tsx/VppPage.tsxfiles in this PR), or - Adding
infocopy clarifying the failure and a retry CTA viaprimaryButton.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@frontend/pages/hosts/details/cards/Software/SelfService/SelfServiceCard/SelfServiceCard.tsx`
around lines 135 - 141, The current SelfServiceCard uses EmptyState when isError
which shows skeleton/ghost UI and can be mistaken for loading; update
SelfServiceCard to render the dedicated error pattern instead — either replace
the EmptyState return for the isError branch with the shared DataError component
(as used in Certificates.tsx / VppPage.tsx) or enhance the EmptyState by adding
explicit error copy via the info prop and a retry primaryButton that invokes the
card's reload handler; target the isError branch in SelfServiceCard and ensure
the chosen component or props clearly convey failure and offer retry.
There was a problem hiding this comment.
@mike-j-thomas - just a consideration for error state
There was a problem hiding this comment.
Makes me wonder why we weren't using our normal error state here. Maybe because it happens often with expired tokens, no idea, so not going to touch this out of scope.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx (1)
86-90:⚠️ Potential issue | 🟡 MinorDead code:
delete emptyState.primaryButtonhas no effect.
primaryButtonis never assigned toemptyStatein this file (onlyheaderandinfoare set at lines 65-69, and onlyheader/infoare mutated in the conditionals above). Thedeleteon line 87 is therefore a no-op — likely a leftover from the previous shape ofemptyState. Either drop the line or, if a CTA is expected on the empty state, setprimaryButtonsomewhere upstream.♻️ Suggested cleanup
if (searchQuery || isFiltered) { - delete emptyState.primaryButton; emptyState.header = "No matching policies"; emptyState.info = "No policies match the current filters."; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx` around lines 86 - 90, The delete on emptyState.primaryButton is dead code because primaryButton is never set in this component; remove the line to clean up. Locate the conditional that checks (searchQuery || isFiltered) in PoliciesTable.tsx and delete the statement "delete emptyState.primaryButton"; if instead a CTA is intended, initialize or assign emptyState.primaryButton where emptyState is constructed (refer to the emptyState object) rather than deleting it here.frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsx (1)
196-199:⚠️ Potential issue | 🔴 CriticalLeftover
delete emptyParams.graphicNamefrom the oldEmptyTableAPI.
graphicNameis no longer part ofIEmptyStatePropsand is never assigned toemptyParamsin this file, so this line is dead code and will fail TypeScript compilation (Property 'graphicName' does not exist on type 'IEmptyStateProps'). This is a stale remnant from theEmptyTablemigration that needs to be removed.🐛 Proposed fix
if (searchQuery || curTargetedPlatformFilter !== "all") { - delete emptyParams.graphicName; emptyParams.header = "No matching reports"; emptyParams.info = "No reports match the current filters."; } else if (!isOnlyObserver || isObserverPlus || isAnyTeamObserverPlus) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsx` around lines 196 - 199, The code contains a stale reference to emptyParams.graphicName which no longer exists on the IEmptyStateProps type and causes a TypeScript error; remove the delete emptyParams.graphicName line from the QueriesTable component (the block that sets emptyParams.header and emptyParams.info when searchQuery or curTargetedPlatformFilter !== "all") so only valid properties on emptyParams are used and the code compiles against the new EmptyTable/IEmptyStateProps API.frontend/interfaces/empty_state.ts (1)
1-8: 🛠️ Refactor suggestion | 🟠 MajorInterface diverges from the canonical
IEmptyStatePropsinEmptyState.tsx.The component at
frontend/components/EmptyState/EmptyState.tsxdefines its ownIEmptyStatePropsthat additionally includeswidth?: "default" | "small"andvariant?: "list" | "header-list" | "form". The version exported here is missing both. Two same-named interfaces diverging will silently allow consumers typed against this file to omit/typo those props, and any future addition (e.g., a new variant) will need to be made in two places.Prefer re-exporting the canonical interface from the component so there is a single source of truth:
♻️ Proposed refactor
-export interface IEmptyStateProps { - header?: JSX.Element | string; - info?: JSX.Element | string; - additionalInfo?: JSX.Element | string; - className?: string; - primaryButton?: JSX.Element; - secondaryButton?: JSX.Element; -} +export type { IEmptyStateProps } from "components/EmptyState/EmptyState";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/interfaces/empty_state.ts` around lines 1 - 8, The exported IEmptyStateProps here diverges from the canonical one defined in the EmptyState component (EmptyState.tsx) which includes width?: "default" | "small" and variant?: "list" | "header-list" | "form"; remove the duplicate interface and re-export the canonical type instead by importing and re-exporting IEmptyStateProps from the EmptyState component so there's a single source of truth (ensure references to IEmptyStateProps in this file now point to the imported/re-exported symbol).
🧹 Nitpick comments (1)
frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx (1)
146-153: Optional: spreademptyStateinstead of enumerating fields.Since
emptyStateis already typed asIEmptyStateProps, you can spread it directly. This also avoids passingadditionalInfo(which is never set in this file and is therefore alwaysundefined) and keeps the call site resilient if more props are added later.♻️ Proposed refactor
- emptyComponent={() => ( - <EmptyState - header={emptyState.header} - info={emptyState.info} - additionalInfo={emptyState.additionalInfo} - primaryButton={emptyState.primaryButton} - /> - )} + emptyComponent={() => <EmptyState {...emptyState} />}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx` around lines 146 - 153, Replace the explicit prop enumeration for the EmptyState render with a single spread of the existing emptyState object: locate the emptyComponent render in PoliciesTable where EmptyState is called (emptyComponent={() => (<EmptyState ... />)}), and change it to pass {...emptyState} so EmptyState receives the typed IEmptyStateProps directly; this removes the redundant additionalInfo={emptyState.additionalInfo} and makes the call resilient to future prop additions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@frontend/interfaces/empty_state.ts`:
- Around line 1-8: The exported IEmptyStateProps here diverges from the
canonical one defined in the EmptyState component (EmptyState.tsx) which
includes width?: "default" | "small" and variant?: "list" | "header-list" |
"form"; remove the duplicate interface and re-export the canonical type instead
by importing and re-exporting IEmptyStateProps from the EmptyState component so
there's a single source of truth (ensure references to IEmptyStateProps in this
file now point to the imported/re-exported symbol).
In
`@frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx`:
- Around line 86-90: The delete on emptyState.primaryButton is dead code because
primaryButton is never set in this component; remove the line to clean up.
Locate the conditional that checks (searchQuery || isFiltered) in
PoliciesTable.tsx and delete the statement "delete emptyState.primaryButton"; if
instead a CTA is intended, initialize or assign emptyState.primaryButton where
emptyState is constructed (refer to the emptyState object) rather than deleting
it here.
In
`@frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsx`:
- Around line 196-199: The code contains a stale reference to
emptyParams.graphicName which no longer exists on the IEmptyStateProps type and
causes a TypeScript error; remove the delete emptyParams.graphicName line from
the QueriesTable component (the block that sets emptyParams.header and
emptyParams.info when searchQuery or curTargetedPlatformFilter !== "all") so
only valid properties on emptyParams are used and the code compiles against the
new EmptyTable/IEmptyStateProps API.
---
Nitpick comments:
In
`@frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx`:
- Around line 146-153: Replace the explicit prop enumeration for the EmptyState
render with a single spread of the existing emptyState object: locate the
emptyComponent render in PoliciesTable where EmptyState is called
(emptyComponent={() => (<EmptyState ... />)}), and change it to pass
{...emptyState} so EmptyState receives the typed IEmptyStateProps directly; this
removes the redundant additionalInfo={emptyState.additionalInfo} and makes the
call resilient to future prop additions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d3e645bc-0c60-466c-9cb4-942c0f201d28
📒 Files selected for processing (12)
frontend/components/EmptyTable/EmptyTable.stories.tsxfrontend/components/EmptyTable/EmptyTable.tsxfrontend/components/EmptyTable/_styles.scssfrontend/components/EmptyTable/index.tsfrontend/interfaces/empty_state.tsfrontend/pages/SoftwarePage/components/tables/EmptySoftwareTable/EmptySoftwareTable.tsxfrontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/EmptyVulnerabilitiesTable/EmptyVulnerabilitiesTable.tsxfrontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsxfrontend/pages/packs/ManagePacksPage/components/PacksTable/PacksTable.tsxfrontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsxfrontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsxfrontend/pages/queries/details/components/NoResults/NoResults.tsx
💤 Files with no reviewable changes (4)
- frontend/components/EmptyTable/index.ts
- frontend/components/EmptyTable/_styles.scss
- frontend/components/EmptyTable/EmptyTable.stories.tsx
- frontend/components/EmptyTable/EmptyTable.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- frontend/pages/SoftwarePage/components/tables/EmptySoftwareTable/EmptySoftwareTable.tsx
- frontend/pages/packs/ManagePacksPage/components/PacksTable/PacksTable.tsx
- frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
CI Feedback 🧐A test triggered by this PR failed. Here is an AI-generated analysis of the failure:
|
lukeheath
left a comment
There was a problem hiding this comment.
✨ 💖 ✨ Looks great!
Much better UX, and the implementation is cleaner and more efficient AND a net reduction of 20 lines of code.
I tried to find something that AI bots hadn't, but no luck. Nice work!
Cherry-pick of #43896 into the RC branch.
Resolves #44301 This PR updates the `fork-ts-checker-webpack-plugin` to resolve the errors we were seeing when running it. Since it now runs cleanly, it identified a few typescript errors that need to be resolved at the same time: * `<EmptyState>` no longer takes a `graphicName` param since it's been redesigned in #43896 * `<PoliciesCard>` was reworked in #43411 and no longer takes `router`. I verified that this compiles cleanly in `make generate` and `make generate-dev`. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Simplified component properties and prop handling across host details, packs, and queries pages. * Refined empty-state configuration in packs management and queries tables. * **Chores** * Updated development build tool dependency from version 6.5.0 to 9.1.0 for TypeScript type-checking integration. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Issue
Closes #35483
Description
Screenrecording
Watch in 2x
https://fleetdm.zoom.us/clips/share/fqdB2Hq9QRGCat3mt-j_wA
Checklist for submitter
If some of the following don't apply, delete the relevant line.
changes/,orbit/changes/oree/fleetd-chrome/changes.See Changes files for more information.
Testing
Summary by CodeRabbit
New Features
Visual Improvements
Chores