feat(FN-4702): complete Step 3 — render worktrunk approval details
Fusion-Task-Id: FN-4702 Fusion-Task-Lineage: 378b46bc-2e71-43bf-9ff7-85e7aed8bf85
This commit is contained in:
committed by
gsxdsm
parent
d733b01925
commit
1eef42a912
@@ -37,6 +37,7 @@ import {
|
|||||||
} from "../api";
|
} from "../api";
|
||||||
import { MailboxMessageContent } from "./MailboxMessageContent";
|
import { MailboxMessageContent } from "./MailboxMessageContent";
|
||||||
import { MessageComposer } from "./MessageComposer";
|
import { MessageComposer } from "./MessageComposer";
|
||||||
|
import { WorktrunkInstallApprovalDetails } from "./WorktrunkInstallApprovalDetails";
|
||||||
import { subscribeSse } from "../sse-bus";
|
import { subscribeSse } from "../sse-bus";
|
||||||
import { useViewportMode } from "../hooks/useViewportMode";
|
import { useViewportMode } from "../hooks/useViewportMode";
|
||||||
import { useMobileKeyboard } from "../hooks/useMobileKeyboard";
|
import { useMobileKeyboard } from "../hooks/useMobileKeyboard";
|
||||||
@@ -1079,6 +1080,9 @@ export function MailboxView({
|
|||||||
{selectedApproval.taskId && <p>Task: {selectedApproval.taskId}</p>}
|
{selectedApproval.taskId && <p>Task: {selectedApproval.taskId}</p>}
|
||||||
<p>Requested: {formatTimestamp(selectedApproval.createdAt)}</p>
|
<p>Requested: {formatTimestamp(selectedApproval.createdAt)}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{selectedApproval.targetAction.category === "network_api" && selectedApproval.targetAction.action === "worktrunk_install" && (
|
||||||
|
<WorktrunkInstallApprovalDetails targetAction={selectedApproval.targetAction} />
|
||||||
|
)}
|
||||||
<div className="mailbox-conversation" data-testid="mailbox-approval-history">
|
<div className="mailbox-conversation" data-testid="mailbox-approval-history">
|
||||||
{selectedApproval.history.map((event) => (
|
{selectedApproval.history.map((event) => (
|
||||||
<div key={event.id} className="mailbox-conversation-msg">
|
<div key={event.id} className="mailbox-conversation-msg">
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
.worktrunk-install-approval-details {
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.worktrunk-install-approval-details__title {
|
||||||
|
margin: 0 0 var(--space-sm) 0;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worktrunk-install-approval-details__list {
|
||||||
|
margin: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.worktrunk-install-approval-details__row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, auto) minmax(0, 1fr);
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.worktrunk-install-approval-details__row dt {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worktrunk-install-approval-details__row dd {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text);
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.worktrunk-install-approval-details__row {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import "./WorktrunkInstallApprovalDetails.css";
|
||||||
|
import type { ApprovalRequestDetail } from "../api";
|
||||||
|
|
||||||
|
interface WorktrunkInstallApprovalDetailsProps {
|
||||||
|
targetAction: ApprovalRequestDetail["targetAction"];
|
||||||
|
}
|
||||||
|
|
||||||
|
function readString(value: unknown): string | null {
|
||||||
|
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readAsset(context: Record<string, unknown> | undefined): { url: string | null; sha256: string | null } {
|
||||||
|
if (!context) return { url: null, sha256: null };
|
||||||
|
const assets = context.assets;
|
||||||
|
if (!assets || typeof assets !== "object") return { url: null, sha256: null };
|
||||||
|
const firstAsset = Object.values(assets as Record<string, unknown>)[0];
|
||||||
|
if (!firstAsset || typeof firstAsset !== "object") return { url: null, sha256: null };
|
||||||
|
const asset = firstAsset as Record<string, unknown>;
|
||||||
|
return {
|
||||||
|
url: readString(asset.url),
|
||||||
|
sha256: readString(asset.sha256),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WorktrunkInstallApprovalDetails({ targetAction }: WorktrunkInstallApprovalDetailsProps) {
|
||||||
|
const context = targetAction.context as Record<string, unknown> | undefined;
|
||||||
|
const version = readString(context?.version);
|
||||||
|
const installPath = readString(context?.installPath) ?? targetAction.resourceId;
|
||||||
|
const { url, sha256 } = readAsset(context);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="card worktrunk-install-approval-details" data-testid="worktrunk-install-approval-details">
|
||||||
|
<h4 className="worktrunk-install-approval-details__title">Worktrunk install request</h4>
|
||||||
|
<dl className="worktrunk-install-approval-details__list">
|
||||||
|
<div className="worktrunk-install-approval-details__row">
|
||||||
|
<dt>Version</dt>
|
||||||
|
<dd>{version ?? "Unknown"}</dd>
|
||||||
|
</div>
|
||||||
|
<div className="worktrunk-install-approval-details__row">
|
||||||
|
<dt>Asset URL</dt>
|
||||||
|
<dd>{url ?? "Unknown"}</dd>
|
||||||
|
</div>
|
||||||
|
<div className="worktrunk-install-approval-details__row">
|
||||||
|
<dt>SHA-256</dt>
|
||||||
|
<dd>{sha256 ?? "Unknown"}</dd>
|
||||||
|
</div>
|
||||||
|
<div className="worktrunk-install-approval-details__row">
|
||||||
|
<dt>Install path</dt>
|
||||||
|
<dd>{installPath ?? "Unknown"}</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -297,6 +297,47 @@ describe("MailboxView", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders worktrunk install details for worktrunk approvals", async () => {
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
mockFetchInbox.mockResolvedValue({ messages: [], unreadCount: 0, total: 0 });
|
||||||
|
mockFetchApprovals.mockResolvedValue({
|
||||||
|
requests: [{ id: "apr-1", status: "pending", actionCategory: "network_api", actionSummary: "Install worktrunk", agentId: "user", createdAt: now, updatedAt: now }],
|
||||||
|
total: 1,
|
||||||
|
pendingCount: 1,
|
||||||
|
});
|
||||||
|
mockFetchApprovalDetail.mockResolvedValue({
|
||||||
|
id: "apr-1", status: "pending", actionCategory: "network_api", actionSummary: "Install worktrunk", agentId: "user", createdAt: now, updatedAt: now,
|
||||||
|
requester: { actorId: "user", actorType: "user", actorName: "User" }, requestedAt: now,
|
||||||
|
targetAction: {
|
||||||
|
category: "network_api",
|
||||||
|
action: "worktrunk_install",
|
||||||
|
summary: "Install worktrunk",
|
||||||
|
resourceType: "binary",
|
||||||
|
resourceId: "~/.fusion/bin/worktrunk",
|
||||||
|
context: {
|
||||||
|
version: "v1.2.3",
|
||||||
|
installPath: "~/.fusion/bin/worktrunk",
|
||||||
|
assets: {
|
||||||
|
darwin_arm64: {
|
||||||
|
url: "https://example.com/worktrunk.tar.gz",
|
||||||
|
sha256: "abc123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
history: [{ id: "evt-1", eventType: "created", actor: { actorId: "user", actorType: "user", actorName: "User" }, createdAt: now }],
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<MailboxView {...defaultProps} />);
|
||||||
|
await act(async () => { fireEvent.click(screen.getByTestId("mailbox-tab-approvals")); });
|
||||||
|
await act(async () => { fireEvent.click(await screen.findByTestId("mailbox-approval-item-apr-1")); });
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("worktrunk-install-approval-details")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("v1.2.3")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("allows approving a pending approval request", async () => {
|
it("allows approving a pending approval request", async () => {
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
mockFetchInbox.mockResolvedValue({ messages: [], unreadCount: 0, total: 0 });
|
mockFetchInbox.mockResolvedValue({ messages: [], unreadCount: 0, total: 0 });
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { WorktrunkInstallApprovalDetails } from "../WorktrunkInstallApprovalDetails";
|
||||||
|
import type { ApprovalRequestDetail } from "../../api";
|
||||||
|
|
||||||
|
describe("WorktrunkInstallApprovalDetails", () => {
|
||||||
|
it("renders version, URL, sha, and install path", () => {
|
||||||
|
const targetAction: ApprovalRequestDetail["targetAction"] = {
|
||||||
|
category: "network_api",
|
||||||
|
action: "worktrunk_install",
|
||||||
|
summary: "Install worktrunk",
|
||||||
|
resourceType: "binary",
|
||||||
|
resourceId: "~/.fusion/bin/worktrunk",
|
||||||
|
context: {
|
||||||
|
version: "v1.2.3",
|
||||||
|
installPath: "~/.fusion/bin/worktrunk",
|
||||||
|
assets: {
|
||||||
|
darwin_arm64: {
|
||||||
|
url: "https://example.com/worktrunk.tar.gz",
|
||||||
|
sha256: "abc123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<WorktrunkInstallApprovalDetails targetAction={targetAction} />);
|
||||||
|
|
||||||
|
expect(screen.getByText("Worktrunk install request")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("v1.2.3")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("https://example.com/worktrunk.tar.gz")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("abc123")).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText("~/.fusion/bin/worktrunk").length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gracefully handles missing context fields", () => {
|
||||||
|
const targetAction: ApprovalRequestDetail["targetAction"] = {
|
||||||
|
category: "network_api",
|
||||||
|
action: "worktrunk_install",
|
||||||
|
summary: "Install worktrunk",
|
||||||
|
resourceType: "binary",
|
||||||
|
resourceId: "~/.fusion/bin/worktrunk",
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<WorktrunkInstallApprovalDetails targetAction={targetAction} />);
|
||||||
|
|
||||||
|
expect(screen.getByTestId("worktrunk-install-approval-details")).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText("Unknown").length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user