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
This commit is contained in:
Martin Angers 2025-04-10 07:25:03 -04:00 committed by GitHub
parent d5ba6a983b
commit e33b2b0a41
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 79 additions and 6 deletions

View file

@ -119,6 +119,7 @@ export enum ActivityType {
export type IHostPastActivityType =
| ActivityType.RanScript
| ActivityType.LockedHost
| ActivityType.WipedHost
| ActivityType.UnlockedHost
| ActivityType.InstalledSoftware
| ActivityType.UninstalledSoftware

View file

@ -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,

View file

@ -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(
<WipeHostActivityItem
activity={createMockHostPastActivity({ actor_full_name: "Test User" })}
tab="past"
/>
);
expect(screen.getByText("Test User")).toBeVisible();
expect(screen.getByText(/wiped this host/i)).toBeVisible();
});
it("does not render the cancel icon", () => {
render(
<WipeHostActivityItem
activity={createMockHostPastActivity({ actor_full_name: "Test User" })}
tab="past"
/>
);
expect(screen.queryByTestId("close-icon")).not.toBeInTheDocument();
});
it("does not render the show details icon", () => {
render(
<WipeHostActivityItem
activity={createMockHostPastActivity({ actor_full_name: "Test User" })}
tab="past"
/>
);
expect(screen.queryByTestId("info-outline-icon")).not.toBeInTheDocument();
});
});

View file

@ -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 (
<ActivityItem
className={baseClass}
activity={activity}
hideCancel
hideShowDetails
>
<b>{activity.actor_full_name}</b> wiped this host.
</ActivityItem>
);
};
export default WipedHostActivityItem;

View file

@ -0,0 +1 @@
export { default } from "./WipedHostActivityItem";

View file

@ -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
`

View file

@ -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: