From e33b2b0a41c754f0143bd531bdc20422c34842c6 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Thu, 10 Apr 2025 07:25:03 -0400 Subject: [PATCH] Cancel UA: Make wiped activity a host-specific activity so it can be deleted when canceled (#28034) For #27409 (unreleased bug) # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated automated tests - [x] Manual QA for all new/changed functionality See https://drive.google.com/file/d/1xg8DM97UJITA0vGUyoOd2esZRfehEgW7/view?usp=drive_link --- frontend/interfaces/activity.ts | 1 + .../details/cards/Activity/ActivityConfig.tsx | 2 + .../WipeHostActivityItem.tests.tsx | 41 +++++++++++++++++++ .../WipedHostActivityItem.tsx | 24 +++++++++++ .../WipedHostActivityItem/index.ts | 1 + server/datastore/mysql/activities.go | 12 +++--- server/fleet/activities.go | 4 ++ 7 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 frontend/pages/hosts/details/cards/Activity/ActivityItems/WipedHostActivityItem/WipeHostActivityItem.tests.tsx create mode 100644 frontend/pages/hosts/details/cards/Activity/ActivityItems/WipedHostActivityItem/WipedHostActivityItem.tsx create mode 100644 frontend/pages/hosts/details/cards/Activity/ActivityItems/WipedHostActivityItem/index.ts diff --git a/frontend/interfaces/activity.ts b/frontend/interfaces/activity.ts index 534de48875..aaa0ea2094 100644 --- a/frontend/interfaces/activity.ts +++ b/frontend/interfaces/activity.ts @@ -119,6 +119,7 @@ export enum ActivityType { export type IHostPastActivityType = | ActivityType.RanScript | ActivityType.LockedHost + | ActivityType.WipedHost | ActivityType.UnlockedHost | ActivityType.InstalledSoftware | ActivityType.UninstalledSoftware diff --git a/frontend/pages/hosts/details/cards/Activity/ActivityConfig.tsx b/frontend/pages/hosts/details/cards/Activity/ActivityConfig.tsx index f93aa2d786..fdd17f8410 100644 --- a/frontend/pages/hosts/details/cards/Activity/ActivityConfig.tsx +++ b/frontend/pages/hosts/details/cards/Activity/ActivityConfig.tsx @@ -12,6 +12,7 @@ import { ShowActivityDetailsHandler } from "components/ActivityItem/ActivityItem import RanScriptActivityItem from "./ActivityItems/RanScriptActivityItem"; import LockedHostActivityItem from "./ActivityItems/LockedHostActivityItem"; +import WipedHostActivityItem from "./ActivityItems/WipedHostActivityItem"; import UnlockedHostActivityItem from "./ActivityItems/UnlockedHostActivityItem"; import InstalledSoftwareActivityItem from "./ActivityItems/InstalledSoftwareActivityItem"; import CanceledRunScriptActivityItem from "./ActivityItems/CanceledRunScriptActivityItem"; @@ -46,6 +47,7 @@ export const pastActivityComponentMap: Record< > = { [ActivityType.RanScript]: RanScriptActivityItem, [ActivityType.LockedHost]: LockedHostActivityItem, + [ActivityType.WipedHost]: WipedHostActivityItem, [ActivityType.UnlockedHost]: UnlockedHostActivityItem, [ActivityType.InstalledSoftware]: InstalledSoftwareActivityItem, [ActivityType.UninstalledSoftware]: InstalledSoftwareActivityItem, diff --git a/frontend/pages/hosts/details/cards/Activity/ActivityItems/WipedHostActivityItem/WipeHostActivityItem.tests.tsx b/frontend/pages/hosts/details/cards/Activity/ActivityItems/WipedHostActivityItem/WipeHostActivityItem.tests.tsx new file mode 100644 index 0000000000..d346d555a4 --- /dev/null +++ b/frontend/pages/hosts/details/cards/Activity/ActivityItems/WipedHostActivityItem/WipeHostActivityItem.tests.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { createMockHostPastActivity } from "__mocks__/activityMock"; + +import WipeHostActivityItem from "./WipedHostActivityItem"; + +describe("WipeHostActivityItem", () => { + it("renders the activity content", () => { + render( + + ); + + expect(screen.getByText("Test User")).toBeVisible(); + expect(screen.getByText(/wiped this host/i)).toBeVisible(); + }); + + it("does not render the cancel icon", () => { + render( + + ); + + expect(screen.queryByTestId("close-icon")).not.toBeInTheDocument(); + }); + + it("does not render the show details icon", () => { + render( + + ); + + expect(screen.queryByTestId("info-outline-icon")).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/pages/hosts/details/cards/Activity/ActivityItems/WipedHostActivityItem/WipedHostActivityItem.tsx b/frontend/pages/hosts/details/cards/Activity/ActivityItems/WipedHostActivityItem/WipedHostActivityItem.tsx new file mode 100644 index 0000000000..ccef24b3c8 --- /dev/null +++ b/frontend/pages/hosts/details/cards/Activity/ActivityItems/WipedHostActivityItem/WipedHostActivityItem.tsx @@ -0,0 +1,24 @@ +import React from "react"; + +import ActivityItem from "components/ActivityItem"; + +import { IHostActivityItemComponentProps } from "../../ActivityConfig"; + +const baseClass = "wiped-host-activity-item"; + +const WipedHostActivityItem = ({ + activity, +}: IHostActivityItemComponentProps) => { + return ( + + {activity.actor_full_name} wiped this host. + + ); +}; + +export default WipedHostActivityItem; diff --git a/frontend/pages/hosts/details/cards/Activity/ActivityItems/WipedHostActivityItem/index.ts b/frontend/pages/hosts/details/cards/Activity/ActivityItems/WipedHostActivityItem/index.ts new file mode 100644 index 0000000000..bca4a908d2 --- /dev/null +++ b/frontend/pages/hosts/details/cards/Activity/ActivityItems/WipedHostActivityItem/index.ts @@ -0,0 +1 @@ +export { default } from "./WipedHostActivityItem"; diff --git a/server/datastore/mysql/activities.go b/server/datastore/mysql/activities.go index 58595a43ae..9c0b92cf6f 100644 --- a/server/datastore/mysql/activities.go +++ b/server/datastore/mysql/activities.go @@ -959,23 +959,23 @@ func clearLockWipeForCanceledActivity(ctx context.Context, tx sqlx.ExtContext, h lockCnt, _ := resLock.RowsAffected() wipeCnt, _ := resWipe.RowsAffected() if lockCnt > 0 || wipeCnt > 0 { - // if it did deleted host_mdm_actions, then it was a lock or wipe activity, - // we need to deleted the "past" activity that gets created immediately + // if it did delete host_mdm_actions, then it was a lock or wipe activity, + // we need to delete the "past" activity that gets created immediately // when that command is queued. actType := fleet.ActivityTypeLockedHost{}.ActivityName() if wipeCnt > 0 { actType = fleet.ActivityTypeWipedHost{}.ActivityName() } - const findActStmt = `SELECT - id + const findActStmt = `SELECT + id FROM - activities + activities INNER JOIN host_activities ON (host_activities.activity_id = activities.id) WHERE host_activities.host_id = ? AND activities.activity_type = ? - ORDER BY + ORDER BY activities.created_at DESC LIMIT 1 ` diff --git a/server/fleet/activities.go b/server/fleet/activities.go index bd9fe958b8..b9369c2790 100644 --- a/server/fleet/activities.go +++ b/server/fleet/activities.go @@ -1597,6 +1597,10 @@ func (a ActivityTypeWipedHost) ActivityName() string { return "wiped_host" } +func (a ActivityTypeWipedHost) HostIDs() []uint { + return []uint{a.HostID} +} + func (a ActivityTypeWipedHost) Documentation() (activity, details, detailsExample string) { return `Generated when a user sends a request to wipe a host.`, `This activity contains the following fields: