Skip to content

Notify before patching: activity feed, details modals, and automation runs #50915

Description

@cdcme

Related user story

#39178

Task

Render the new notification activities in the global feed, the host feed, the details modals, and the policy Automation runs table. Each failure cause gets its own sentence, so an admin can tell "deploy Fleet Desktop" apart from "the screen was locked."

Depends on the patch notification kind, which defines the activity payload.

Activity feed

frontend/pages/DashboardPage/cards/ActivityFeed/GlobalActivityItem.tsx handles the global feed. The patch-when-closed skip is already handled at line 1529, which is the pattern to copy.

Global feed:

  • Fleet notified end user 1 hour before patching **1Password**, **Slack**, and **Docker Desktop** on **John's MacBook Pro**.
  • Fleet failed to notify end user 1 hour before patching **1Password** on **Josh's MacBook Pro**.
  • The reminder uses 5 minutes in place of 1 hour, driven by time_before (3600 or 300).

Host feed (frontend/pages/hosts/details/cards/Activity/ActivityItems/, alongside InstalledSoftwareActivityItem.tsx:34) drops the host name:

  • Fleet notified end user 1 hour before the **1Password** patch on this host.

Truncate past three apps. List the first three, then , and <n> more apps. Figma dev note 5546:46306. The details modal lists all of them.

Bold the software titles and the host name. Use getDisplayedSoftwareName for the label and the raw name for any icon, per the frontend conventions.

Details modals

The activity payload carries no reason field. Marko settled this on the parent story: the modal fetches the notification script's result and picks its copy from the exit code.

The activity carries script_execution_id (#50679, and Figma dev note 5582:46519). Fetch the run with GET /api/v1/fleet/scripts/results/{execution_id}, which returns exit_code and output. SoftwareUninstallDetailsModal.tsx:191 already does exactly this with a scriptExecutionId, so follow it.

Exit code Text
0 If the host is offline when the patch is forced, Fleet skips the patch. When the host comes back online Fleet notifies the end user again and the patch is forced 1 hour later.
100 The Fleet Desktop app is required to notify end users. Add the app from the Fleet-maintained catalog and deploy to all your hosts.
101 The Fleet Desktop app v1.5.0 is required to notify end users. Add the app from the Fleet-maintained catalog and deploy to all your hosts.
30, 31 The notification couldn't load. Fleet will try again on the next policy run.
41 The screen was locked so the end user couldn't see the notification. Fleet will try again on the next policy run.
the deferred code, number TBC Another notification was displayed. Fleet will try again on the next policy run.
no script_execution_id same sentence, see below
anything else no sentence, output only

Deferred arrives two ways, and both render the same sentence. Marko added a notify exit code for "another notification is displayed" on 2026-08-11, so the binary reports it when it catches the condition. When the dispatcher catches it first no script runs at all, so there is no execution and no exit code, and absence of script_execution_id is the only signal. Absence is unambiguous because the patch kind sets no expires_at, making a server-side deferral the only activity-emitting outcome with no script.

Get the exit code number from Marko before implementing.

Default to no sentence. Exit codes get added over time, and a missing case must render the output rather than crash or show a wrong cause.

The modal also shows an Apps row listing every title, and the script output under Notification script output:.

Skip details copy

The app-open skip reuses the existing installed_software activity for both patch_when_closed and notify_before_patching. The two are distinguished by pre_install_query_output, which #50677 documents as different text per flag:

Flag Output
patch_when_closed Query didn't return result\nThe app was open.
notify_before_patching Query didn't return result\nThe app was open. Fleet notifies the end user 1 hour before the patch is forced.

Render the extra copy from that output rather than adding a field. Marko confirmed this on the parent story.

Host activity details modals reuse the global ones. Figma dev note 5546:43145.

Automation runs

frontend/pages/policies/details/components/PolicyAutomationsActivitiesTable/. The helpers at helpers.tsx:25 and :64 already map the app-open skip to a distinct label and a grey icon. Add:

Automation Icon Details
End user notified (1Password) grey error-outline End user was notified. Patch will be forced in 1 hour. If the host is offline when a patch should be forced, Fleet notifies the end user again when it comes back online and patches it after 1 hour.
Failed to notify (1Password) red error-outline the sentence for that exit code, from the table above

The reminder row reads Patch will be forced in 5 minutes.

The existing Install skipped (1Password) row keeps its copy: The app was open. The end user will be notified before the patch is forced. Check whether it needs updating now that notification is a real outcome.

One notification, several rows. A notification covering three policies appears in the Automation runs table of all three, showing that policy's app name. The backend join handles this; the table renders whatever it returns.

Interfaces

Add notified_end_user_before_patching to ActivityType in frontend/interfaces/activity.ts, and its payload fields to IActivityDetails. One type, not two: status distinguishes displayed from failed.

Both names already exist, and they mean different things. The activity payload field is install_skipped_when_app_open (server/fleet/activities.go:1219). skipped_install is the frontend display status derived from it, at frontend/interfaces/software.ts:483 and ActivityDetails/InstallDetails/constants.ts:22,38. Reuse both as they are. The merged doc #49106 conflates them by documenting the payload field as skipped_install; that is a doc fix, tracked in #50918.

Condition of satisfaction

Global feed

  • One app, three apps, and five apps each render correctly, with the , and 2 more apps suffix past three.
  • time_before: 3600 renders 1 hour; 300 renders 5 minutes.
  • A failure renders the failed to notify sentence.
  • Software titles and the host name are bold.

Host feed

  • The same activities render without the host name, ending on this host.

Details modals

  • Each documented exit code renders its sentence. An undocumented exit code renders output only, and does not crash.
  • An activity with no script_execution_id renders the deferred sentence and makes no script-results request.
  • An activity carrying the deferred exit code renders the same sentence.
  • The skip details copy differs between a patch_when_closed skip and a notify_before_patching skip, driven by pre_install_query_output.
  • The Apps row lists every app, including past three.
  • Notification script output is shown.
  • Host and global modals render identically.

Automation runs

  • End user notified and Failed to notify rows appear with the right icon colour and details text.
  • The reminder row says 5 minutes.
  • A notification covering three policies appears in all three tables.
  • Regression: Install skipped and the existing rows are unchanged.

Tests

  • yarn test extends GlobalActivityItem.tests.tsx, InstalledSoftwareActivityItem.tests.tsx, SoftwareInstallDetailsModal.tests.tsx, and PolicyAutomationsActivitiesTable.tests.tsx.
  • npx tsc --noEmit -p tsconfig.json
  • make lint-js

Metadata

Metadata

Assignees

Labels

#g-auto-patchingProduct group focused on auto patching software~frontendFrontend-related issue.~sub-taskA technical sub-task that is part of a story. (Not QA'd. Not estimated.)

Type

Projects

Status
🥚 Ready

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions