) {
return columns.map(
(column) =>
(column as { id?: string; accessorKey?: string }).id ??
(column as { id?: string; accessorKey?: string }).accessorKey,
);
}
function renderResourceActionsCell({
resource = makeResource(),
onSkillLaunchOpenDrawer,
onTriageUpdateAction,
onTriageNoteLoadAction,
}: {
resource?: FindingResourceRow;
onSkillLaunchOpenDrawer?: Parameters<
typeof getColumnFindingResources
>[0]["onSkillLaunchOpenDrawer"];
onTriageUpdateAction?: Parameters<
typeof getColumnFindingResources
>[0]["onTriageUpdateAction"];
onTriageNoteLoadAction?: Parameters<
typeof getColumnFindingResources
>[0]["onTriageNoteLoadAction"];
} = {}) {
const columns = getColumnFindingResources({
rowSelection: {},
selectableRowCount: 1,
onSkillLaunchOpenDrawer,
onTriageUpdateAction,
onTriageNoteLoadAction,
});
const actionsColumn = columns.find(
(col) => (col as { id?: string }).id === "actions",
);
if (!actionsColumn?.cell) {
throw new Error("actions column not found");
}
const CellComponent = actionsColumn.cell as (props: {
row: { original: FindingResourceRow; index: number };
}) => ReactNode;
render({CellComponent({ row: { original: resource, index: 0 } })}
);
}
describe("column-finding-resources", () => {
beforeEach(() => {
vi.clearAllMocks();
isCloudMock.mockReturnValue(false);
isGroupedJiraDispatchEnabledMock.mockReturnValue(true);
useJiraDispatchStore.getState().closeJiraDispatch();
});
it("opens the finding drawer and launches a row skill with full context", async () => {
// Given
const user = userEvent.setup();
const onSkillLaunchOpenDrawer = vi.fn();
isCloudMock.mockReturnValue(true);
renderResourceActionsCell({ onSkillLaunchOpenDrawer });
// When
await user.click(screen.getByRole("button", { name: "Triage Decision" }));
// Then
expect(onSkillLaunchOpenDrawer).toHaveBeenCalledWith(0);
expect(launchSkillMock).toHaveBeenCalledWith(
expect.objectContaining({ id: "triage-decision" }),
expect.objectContaining({
kind: "finding",
findingId: "finding-1",
checkId: "s3_check",
providerUid: "123456789",
resourceUid: "arn:aws:s3:::my-bucket",
region: "us-east-1",
}),
);
expect(onSkillLaunchOpenDrawer.mock.invocationCallOrder[0]).toBeLessThan(
launchSkillMock.mock.invocationCallOrder[0],
);
});
it("should render actions as the last visible column after Triage without Notes", () => {
// Given
const columns = getColumnFindingResources({
rowSelection: {},
selectableRowCount: 1,
});
// When
const columnIds = getColumnIds(columns);
// Then
expect(columnIds.slice(-2)).toEqual(["triage", "actions"]);
expect(columnIds).not.toContain("status");
expect(columnIds).not.toContain("notes");
expect(
(columns.at(-1) as { id?: string; size?: number } | undefined)?.size,
).toBe(56);
});
it("should render Open note in resource actions without exposing note preview metadata", () => {
// Given
renderResourceActionsCell({
resource: makeResource({
triage: makeTriageSummary({ hasVisibleNote: true }),
}),
onTriageUpdateAction: vi.fn(),
onTriageNoteLoadAction: vi.fn(),
});
// Then
expect(screen.getByRole("button", { name: "Open note" })).toBeEnabled();
expect(screen.queryByText("Sensitive note body")).not.toBeInTheDocument();
expect(screen.queryByText(/author/i)).not.toBeInTheDocument();
expect(screen.queryByText(/timestamp/i)).not.toBeInTheDocument();
});
it("should disable Add Triage Note when no update handler is wired", () => {
// Given
renderResourceActionsCell({
resource: makeResource({
triage: makeTriageSummary({ hasVisibleNote: false }),
}),
});
// Then
expect(
screen.getByRole("button", { name: "Add Triage Note" }),
).toBeDisabled();
});
it("should enable Add Triage Note when an update handler is wired", () => {
// Given
renderResourceActionsCell({
resource: makeResource({
triage: makeTriageSummary({ hasVisibleNote: false }),
}),
onTriageUpdateAction: vi.fn(),
});
// Then
expect(
screen.getByRole("button", { name: "Add Triage Note" }),
).toBeEnabled();
});
it("should enable Add Triage Note for Cloud-only rows so users can open the billing upsell modal", () => {
// Given
renderResourceActionsCell({
resource: makeResource({
triage: makeTriageSummary({
canEdit: false,
hasVisibleNote: false,
disabledReason: FINDING_TRIAGE_DISABLED_REASON.CLOUD_ONLY,
}),
}),
});
// Then
expect(
screen.getByRole("button", { name: "Add Triage Note" }),
).toBeEnabled();
});
it("should disable editable triage control when no update handler is wired", () => {
// Given
const columns = getColumnFindingResources({
rowSelection: {},
selectableRowCount: 1,
});
const triageColumn = columns.find(
(col) => (col as { id?: string }).id === "triage",
);
if (!triageColumn?.cell) {
throw new Error("triage column not found");
}
const CellComponent = triageColumn.cell as (props: {
row: { original: FindingResourceRow };
}) => ReactNode;
// When
render(
{CellComponent({
row: {
original: makeResource({
triage: makeTriageSummary({ canEdit: true }),
}),
},
})}
,
);
// Then
expect(
screen.getByRole("button", { name: "Triage status" }),
).toBeDisabled();
expect(screen.getByText(EDITING_UNAVAILABLE_COPY)).toBeInTheDocument();
});
it("should keep the compact Triage label on resource cells for headerless nested rows", () => {
// Given
const columns = getColumnFindingResources({
rowSelection: {},
selectableRowCount: 1,
});
const triageColumn = columns.find(
(col) => (col as { id?: string }).id === "triage",
);
if (!triageColumn?.cell) {
throw new Error("triage column not found");
}
const CellComponent = triageColumn.cell as (props: {
row: { original: FindingResourceRow };
}) => ReactNode;
// When
render(
{CellComponent({
row: {
original: makeResource({ triage: makeTriageSummary() }),
},
})}
,
);
// Then — expanded finding-group rows render without a header row, so the
// cell itself must carry the label, like Service/Region/Last seen do.
expect(screen.getByText("Triage")).toBeInTheDocument();
});
it("should disable non-paying Cloud triage control with only-in-Cloud tooltip copy", () => {
// Given
const columns = getColumnFindingResources({
rowSelection: {},
selectableRowCount: 1,
});
const triageColumn = columns.find(
(col) => (col as { id?: string }).id === "triage",
);
if (!triageColumn?.cell) {
throw new Error("triage column not found");
}
const CellComponent = triageColumn.cell as (props: {
row: { original: FindingResourceRow };
}) => ReactNode;
// When
render(
{CellComponent({
row: {
original: makeResource({
triage: makeTriageSummary({
canEdit: false,
disabledReason: FINDING_TRIAGE_DISABLED_REASON.CLOUD_ONLY,
}),
}),
},
})}
,
);
// Then
expect(
screen.getByRole("button", { name: "Triage status" }),
).toBeDisabled();
expect(screen.getByText(CLOUD_ONLY_TOOLTIP_COPY)).toBeInTheDocument();
});
it("should open Jira dispatch with the finding UUID directly", async () => {
// Given
const user = userEvent.setup();
const columns = getColumnFindingResources({
rowSelection: {},
selectableRowCount: 1,
});
const actionColumn = columns.find(
(col) => (col as { id?: string }).id === "actions",
);
if (!actionColumn?.cell) {
throw new Error("actions column not found");
}
const CellComponent = actionColumn.cell as (props: {
row: { original: FindingResourceRow };
}) => ReactNode;
render(
{CellComponent({
row: {
original: makeResource({
findingId: "real-finding-uuid",
}),
},
})}
,
);
// When
await user.click(
screen.getByRole("button", { name: "Send 1 Finding to Jira" }),
);
// Then
expect(useJiraDispatchStore.getState().activePayload?.selection).toEqual({
kind: "single",
targetId: "real-finding-uuid",
targetType: "finding_id",
});
});
});