Fix policy automation activities missing from host activity feed

Closes #48616

Adds missing policy automation activities to host's details page.
This commit is contained in:
Juan Fernandez
2026-07-07 14:35:07 -04:00
committed by GitHub
parent 4608e82481
commit 156302adb4
5 changed files with 167 additions and 1 deletions
+9 -1
View File
@@ -228,7 +228,15 @@ export type IHostPastActivityType =
| ActivityType.RotatedManagedLocalAccountPassword
| ActivityType.FailedToRotateManagedLocalAccountPassword
| ActivityType.FailedEnrollmentProfileRenewal
| ActivityType.RanCustomMdmCommand;
| ActivityType.RanCustomMdmCommand
| ActivityType.RanAutomationWebhook
| ActivityType.RanAutomationTicket
| ActivityType.RanAutomationCalendarEvent
| ActivityType.RanAutomationConditionalAccess
| ActivityType.FailedAutomationWebhook
| ActivityType.FailedAutomationTicket
| ActivityType.FailedAutomationCalendarEvent
| ActivityType.FailedAutomationConditionalAccess;
/** This is a subset of ActivityType that are shown only for the host upcoming activities */
export type IHostUpcomingActivityType =
@@ -36,6 +36,7 @@ import FailedToRotateManagedLocalAccountPasswordActivityItem from "./ActivityIte
import FailedEnrollmentProfileRenewalActivityItem from "./ActivityItems/FailedEnrollmentProfileRenewalActivityItem";
import MdmUnenrolledActivityItem from "./ActivityItems/MdmUnenrolledActivityItem";
import RanCustomMdmCommandActivityItem from "./ActivityItems/RanCustomMdmCommandActivityItem";
import PolicyAutomationActivityItem from "./ActivityItems/PolicyAutomationActivityItem";
/** The component props that all host activity items must adhere to */
export interface IHostActivityItemComponentProps {
@@ -92,6 +93,14 @@ export const pastActivityComponentMap: Record<
[ActivityType.FailedEnrollmentProfileRenewal]: FailedEnrollmentProfileRenewalActivityItem,
[ActivityType.MdmUnenrolled]: MdmUnenrolledActivityItem,
[ActivityType.RanCustomMdmCommand]: RanCustomMdmCommandActivityItem,
[ActivityType.RanAutomationWebhook]: PolicyAutomationActivityItem,
[ActivityType.RanAutomationTicket]: PolicyAutomationActivityItem,
[ActivityType.RanAutomationCalendarEvent]: PolicyAutomationActivityItem,
[ActivityType.RanAutomationConditionalAccess]: PolicyAutomationActivityItem,
[ActivityType.FailedAutomationWebhook]: PolicyAutomationActivityItem,
[ActivityType.FailedAutomationTicket]: PolicyAutomationActivityItem,
[ActivityType.FailedAutomationCalendarEvent]: PolicyAutomationActivityItem,
[ActivityType.FailedAutomationConditionalAccess]: PolicyAutomationActivityItem,
};
export const upcomingActivityComponentMap: Record<
@@ -0,0 +1,83 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { createMockHostPastActivity } from "__mocks__/activityMock";
import { ActivityType, IHostPastActivityType } from "interfaces/activity";
import PolicyAutomationActivityItem from "./PolicyAutomationActivityItem";
const renderItem = (type: IHostPastActivityType) =>
render(
<PolicyAutomationActivityItem
activity={createMockHostPastActivity({
type,
actor_full_name: "",
actor_id: 0,
fleet_initiated: true,
details: {},
})}
tab="past"
/>
);
describe("PolicyAutomationActivityItem", () => {
const cases: Array<[IHostPastActivityType, RegExp]> = [
[ActivityType.RanAutomationWebhook, /sent a webhook because this host/i],
[ActivityType.RanAutomationTicket, /created a ticket because this host/i],
[
ActivityType.RanAutomationCalendarEvent,
/created a calendar event because this host/i,
],
[
ActivityType.RanAutomationConditionalAccess,
/blocked single sign-on because this host/i,
],
[
ActivityType.FailedAutomationWebhook,
/failed to send a webhook after this host/i,
],
[
ActivityType.FailedAutomationTicket,
/failed to create a ticket after this host/i,
],
[
ActivityType.FailedAutomationCalendarEvent,
/failed to create a calendar event after this host/i,
],
[
ActivityType.FailedAutomationConditionalAccess,
/failed to block single sign-on after this host/i,
],
];
it.each(cases)("renders copy for %s", (type, expected) => {
renderItem(type);
expect(screen.getByText("Fleet")).toBeVisible();
expect(screen.getByText(expected)).toBeVisible();
});
it("does not render the cancel or show-details icons", () => {
renderItem(ActivityType.RanAutomationWebhook);
expect(screen.queryByTestId("close-icon")).not.toBeInTheDocument();
expect(screen.queryByTestId("info-outline-icon")).not.toBeInTheDocument();
});
// These activities are always Fleet-initiated in practice; this documents the
// defensive actor branch that keeps the bold name in sync with the avatar.
it("renders the actor name when the activity is not Fleet-initiated", () => {
render(
<PolicyAutomationActivityItem
activity={createMockHostPastActivity({
type: ActivityType.RanAutomationWebhook,
actor_full_name: "Admin User",
actor_id: 1,
fleet_initiated: false,
details: {},
})}
tab="past"
/>
);
expect(screen.getByText("Admin User")).toBeVisible();
expect(screen.queryByText("Fleet")).not.toBeInTheDocument();
});
});
@@ -0,0 +1,65 @@
import React from "react";
import { ActivityType, IHostPastActivityType } from "interfaces/activity";
import ActivityItem from "components/ActivityItem";
import { IHostActivityItemComponentProps } from "../../ActivityConfig";
const baseClass = "policy-automation-activity-item";
type PolicyAutomationActivityType =
| ActivityType.RanAutomationWebhook
| ActivityType.RanAutomationTicket
| ActivityType.RanAutomationCalendarEvent
| ActivityType.RanAutomationConditionalAccess
| ActivityType.FailedAutomationWebhook
| ActivityType.FailedAutomationTicket
| ActivityType.FailedAutomationCalendarEvent
| ActivityType.FailedAutomationConditionalAccess;
const AUTOMATION_COPY: Record<PolicyAutomationActivityType, string> = {
[ActivityType.RanAutomationWebhook]:
"sent a webhook because this host failed a policy.",
[ActivityType.RanAutomationTicket]:
"created a ticket because this host failed a policy.",
[ActivityType.RanAutomationCalendarEvent]:
"created a calendar event because this host failed a policy.",
[ActivityType.RanAutomationConditionalAccess]:
"blocked single sign-on because this host failed a policy.",
[ActivityType.FailedAutomationWebhook]:
"failed to send a webhook after this host failed a policy.",
[ActivityType.FailedAutomationTicket]:
"failed to create a ticket after this host failed a policy.",
[ActivityType.FailedAutomationCalendarEvent]:
"failed to create a calendar event after this host failed a policy.",
[ActivityType.FailedAutomationConditionalAccess]:
"failed to block single sign-on after this host failed a policy.",
};
const isPolicyAutomationActivityType = (
type: IHostPastActivityType
): type is PolicyAutomationActivityType => type in AUTOMATION_COPY;
const PolicyAutomationActivityItem = ({
activity,
isSoloActivity,
}: IHostActivityItemComponentProps) => {
if (!isPolicyAutomationActivityType(activity.type)) {
return null;
}
return (
<ActivityItem
className={baseClass}
activity={activity}
isSoloActivity={isSoloActivity}
hideCancel
hideShowDetails
>
<b>{activity.fleet_initiated ? "Fleet" : activity.actor_full_name}</b>{" "}
{AUTOMATION_COPY[activity.type]}
</ActivityItem>
);
};
export default PolicyAutomationActivityItem;
@@ -0,0 +1 @@
export { default } from "./PolicyAutomationActivityItem";