Merge upstream/master into dev (13 commits — includes #5922, #5938, blocked inbox, recovery actions)

This commit is contained in:
2026-05-13 22:35:18 -04:00
180 changed files with 31626 additions and 545 deletions
+20
View File
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
const mockApi = vi.hoisted(() => ({
get: vi.fn(),
post: vi.fn(),
}));
vi.mock("./client", () => ({
@@ -13,7 +14,9 @@ import { issuesApi } from "./issues";
describe("issuesApi.list", () => {
beforeEach(() => {
mockApi.get.mockReset();
mockApi.post.mockReset();
mockApi.get.mockResolvedValue([]);
mockApi.post.mockResolvedValue({});
});
it("passes parentId through to the company issues endpoint", async () => {
@@ -47,4 +50,21 @@ describe("issuesApi.list", () => {
"/companies/company-1/issues?limit=500&offset=1500",
);
});
it("posts recovery action resolution to the source issue endpoint", async () => {
await issuesApi.resolveRecoveryAction("issue-1", {
actionId: "00000000-0000-0000-0000-0000000000aa",
outcome: "restored",
sourceIssueStatus: "done",
});
expect(mockApi.post).toHaveBeenCalledWith(
"/issues/issue-1/recovery-actions/resolve",
{
actionId: "00000000-0000-0000-0000-0000000000aa",
outcome: "restored",
sourceIssueStatus: "done",
},
);
});
});
+41
View File
@@ -12,6 +12,7 @@ import type {
IssueComment,
IssueDocument,
IssueLabel,
IssueRecoveryAction,
IssueRetryNowResponse,
IssueThreadInteraction,
IssueTreeControlPreview,
@@ -27,10 +28,16 @@ export type IssueUpdateResponse = Issue & {
comment?: IssueComment | null;
};
export type ResolveRecoveryActionResponse = {
issue: Issue;
recoveryAction: IssueRecoveryAction;
};
export const issuesApi = {
list: (
companyId: string,
filters?: {
attention?: "blocked";
status?: string;
projectId?: string;
parentId?: string;
@@ -49,12 +56,14 @@ export const issuesApi = {
descendantOf?: string;
includeRoutineExecutions?: boolean;
includeBlockedBy?: boolean;
includeBlockedInboxAttention?: boolean;
q?: string;
limit?: number;
offset?: number;
},
) => {
const params = new URLSearchParams();
if (filters?.attention) params.set("attention", filters.attention);
if (filters?.status) params.set("status", filters.status);
if (filters?.projectId) params.set("projectId", filters.projectId);
if (filters?.parentId) params.set("parentId", filters.parentId);
@@ -73,12 +82,35 @@ export const issuesApi = {
if (filters?.descendantOf) params.set("descendantOf", filters.descendantOf);
if (filters?.includeRoutineExecutions) params.set("includeRoutineExecutions", "true");
if (filters?.includeBlockedBy) params.set("includeBlockedBy", "true");
if (filters?.includeBlockedInboxAttention) params.set("includeBlockedInboxAttention", "true");
if (filters?.q) params.set("q", filters.q);
if (filters?.limit) params.set("limit", String(filters.limit));
if (filters?.offset !== undefined) params.set("offset", String(filters.offset));
const qs = params.toString();
return api.get<Issue[]>(`/companies/${companyId}/issues${qs ? `?${qs}` : ""}`);
},
count: (
companyId: string,
filters: {
attention: "blocked";
status?: string;
assigneeAgentId?: string;
assigneeUserId?: string;
projectId?: string;
labelId?: string;
q?: string;
},
) => {
const params = new URLSearchParams();
params.set("attention", filters.attention);
if (filters.status) params.set("status", filters.status);
if (filters.assigneeAgentId) params.set("assigneeAgentId", filters.assigneeAgentId);
if (filters.assigneeUserId) params.set("assigneeUserId", filters.assigneeUserId);
if (filters.projectId) params.set("projectId", filters.projectId);
if (filters.labelId) params.set("labelId", filters.labelId);
if (filters.q) params.set("q", filters.q);
return api.get<{ count: number }>(`/companies/${companyId}/issues/count?${params.toString()}`);
},
listLabels: (companyId: string) => api.get<IssueLabel[]>(`/companies/${companyId}/labels`),
createLabel: (companyId: string, data: { name: string; color: string }) =>
api.post<IssueLabel>(`/companies/${companyId}/labels`, data),
@@ -94,6 +126,15 @@ export const issuesApi = {
api.post<Issue>(`/companies/${companyId}/issues`, data),
update: (id: string, data: Record<string, unknown>) =>
api.patch<IssueUpdateResponse>(`/issues/${id}`, data),
resolveRecoveryAction: (
id: string,
data: {
actionId?: string;
outcome: "restored" | "false_positive" | "blocked" | "cancelled";
sourceIssueStatus: "done" | "in_review" | "blocked";
resolutionNote?: string | null;
},
) => api.post<ResolveRecoveryActionResponse>(`/issues/${id}/recovery-actions/resolve`, data),
previewTreeControl: (id: string, data: PreviewIssueTreeControl) =>
api.post<IssueTreeControlPreview>(`/issues/${id}/tree-control/preview`, data),
createTreeHold: (id: string, data: CreateIssueTreeHold) =>