fix(web): add mutation retry handling
This commit is contained in:
parent
1fb682a77a
commit
454b2003e9
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { createNotesApiClient } from "@/lib/api-helpers";
|
||||
import { RETRY_WHEN_ONLINE_MESSAGE, withMutationRetry } from "@/lib/mutation-retry";
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────
|
||||
|
||||
@ -63,9 +64,12 @@ export async function submitIntake(
|
||||
const payload: Record<string, string> = { url };
|
||||
if (workspaceId) payload.workspaceId = workspaceId;
|
||||
if (templateOverride) payload.templateOverride = templateOverride;
|
||||
return api.fetch("/intake", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
return withMutationRetry({
|
||||
run: () => api.fetch("/intake", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
offlineMessage: RETRY_WHEN_ONLINE_MESSAGE,
|
||||
});
|
||||
}
|
||||
|
||||
@ -98,16 +102,23 @@ export async function createIntakeRule(
|
||||
rule: Omit<IntakeRule, "id" | "userId">,
|
||||
): Promise<IntakeRule> {
|
||||
const api = createNotesApiClient();
|
||||
return api.fetch("/intake-rules", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(rule),
|
||||
return withMutationRetry({
|
||||
run: () => api.fetch("/intake-rules", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(rule),
|
||||
}),
|
||||
queue: { id: rule.name, action: "post", path: "/intake-rules", payload: rule },
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteIntakeRule(id: string): Promise<void> {
|
||||
const api = createNotesApiClient();
|
||||
await api.fetch(`/intake-rules/${encodeURIComponent(id)}`, {
|
||||
method: "DELETE",
|
||||
const path = `/intake-rules/${encodeURIComponent(id)}`;
|
||||
await withMutationRetry({
|
||||
run: () => api.fetch(path, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
queue: { id, action: "delete", path, payload: {} },
|
||||
});
|
||||
}
|
||||
|
||||
@ -120,9 +131,13 @@ export async function shareNoteWithUser(
|
||||
permission: "view" | "comment" | "edit" = "view",
|
||||
): Promise<unknown> {
|
||||
const api = createNotesApiClient();
|
||||
return api.fetch(`/notes/${encodeURIComponent(noteId)}/share-with-user`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ workspaceId, sharedWithUserId, permission }),
|
||||
const path = `/notes/${encodeURIComponent(noteId)}/share-with-user`;
|
||||
return withMutationRetry({
|
||||
run: () => api.fetch(path, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ workspaceId, sharedWithUserId, permission }),
|
||||
}),
|
||||
offlineMessage: RETRY_WHEN_ONLINE_MESSAGE,
|
||||
});
|
||||
}
|
||||
|
||||
@ -146,8 +161,12 @@ export async function removeCollaborator(
|
||||
userId: string,
|
||||
): Promise<void> {
|
||||
const api = createNotesApiClient();
|
||||
await api.fetch(`/notes/${encodeURIComponent(noteId)}/collaborators/${encodeURIComponent(userId)}`, {
|
||||
method: "DELETE",
|
||||
const path = `/notes/${encodeURIComponent(noteId)}/collaborators/${encodeURIComponent(userId)}`;
|
||||
await withMutationRetry({
|
||||
run: () => api.fetch(path, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
offlineMessage: RETRY_WHEN_ONLINE_MESSAGE,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
72
web/src/lib/mutation-retry.test.ts
Normal file
72
web/src/lib/mutation-retry.test.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { OFFLINE_QUEUE_MESSAGE, RETRY_WHEN_ONLINE_MESSAGE, withMutationRetry } from "@/lib/mutation-retry";
|
||||
|
||||
const enqueueMock = vi.fn();
|
||||
|
||||
vi.mock("@/lib/offline-queue", () => ({
|
||||
getOfflineQueue: () => ({ enqueue: enqueueMock }),
|
||||
}));
|
||||
|
||||
function setOnline(value: boolean) {
|
||||
Object.defineProperty(navigator, "onLine", {
|
||||
configurable: true,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
describe("withMutationRetry", () => {
|
||||
beforeEach(() => {
|
||||
enqueueMock.mockClear();
|
||||
setOnline(true);
|
||||
});
|
||||
|
||||
it("returns the mutation result when the operation succeeds", async () => {
|
||||
await expect(withMutationRetry({ run: async () => "ok" })).resolves.toBe("ok");
|
||||
expect(enqueueMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("queues retryable mutations while offline and throws a clear queued message", async () => {
|
||||
setOnline(false);
|
||||
|
||||
await expect(withMutationRetry({
|
||||
run: async () => { throw new Error("Failed to fetch"); },
|
||||
queue: {
|
||||
id: "note-1",
|
||||
action: "patch",
|
||||
path: "/notes/note-1?workspaceId=ws-1",
|
||||
payload: { title: "Offline edit" },
|
||||
},
|
||||
})).rejects.toThrow(OFFLINE_QUEUE_MESSAGE);
|
||||
|
||||
expect(enqueueMock).toHaveBeenCalledWith({
|
||||
id: "note-1",
|
||||
action: "patch",
|
||||
path: "/notes/note-1?workspaceId=ws-1",
|
||||
payload: { title: "Offline edit" },
|
||||
});
|
||||
});
|
||||
|
||||
it("uses clear retry UX for offline mutations that need immediate server results", async () => {
|
||||
setOnline(false);
|
||||
|
||||
await expect(withMutationRetry({
|
||||
run: async () => { throw new Error("Failed to fetch"); },
|
||||
})).rejects.toThrow(RETRY_WHEN_ONLINE_MESSAGE);
|
||||
|
||||
expect(enqueueMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not queue validation or server errors while online", async () => {
|
||||
await expect(withMutationRetry({
|
||||
run: async () => { throw new Error("Validation failed"); },
|
||||
queue: {
|
||||
id: "note-1",
|
||||
action: "patch",
|
||||
path: "/notes/note-1",
|
||||
payload: {},
|
||||
},
|
||||
})).rejects.toThrow("Validation failed");
|
||||
|
||||
expect(enqueueMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
44
web/src/lib/mutation-retry.ts
Normal file
44
web/src/lib/mutation-retry.ts
Normal file
@ -0,0 +1,44 @@
|
||||
"use client";
|
||||
|
||||
import { getOfflineQueue } from "@/lib/offline-queue";
|
||||
|
||||
export const OFFLINE_QUEUE_MESSAGE = "You are offline. This change was saved and will retry automatically.";
|
||||
export const RETRY_WHEN_ONLINE_MESSAGE = "You are offline. Reconnect and try again.";
|
||||
|
||||
type QueueAction = "post" | "patch" | "delete" | "create" | "update";
|
||||
|
||||
interface RetryableMutation<T> {
|
||||
run: () => Promise<T>;
|
||||
queue?: {
|
||||
id: string;
|
||||
action: QueueAction;
|
||||
path: string;
|
||||
payload: Record<string, unknown>;
|
||||
};
|
||||
offlineMessage?: string;
|
||||
}
|
||||
|
||||
export function isOffline(): boolean {
|
||||
return typeof navigator !== "undefined" && navigator.onLine === false;
|
||||
}
|
||||
|
||||
export async function withMutationRetry<T>({
|
||||
run,
|
||||
queue,
|
||||
offlineMessage,
|
||||
}: RetryableMutation<T>): Promise<T> {
|
||||
try {
|
||||
return await run();
|
||||
} catch (error) {
|
||||
if (!isOffline()) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (queue) {
|
||||
getOfflineQueue().enqueue(queue);
|
||||
throw new Error(offlineMessage ?? OFFLINE_QUEUE_MESSAGE);
|
||||
}
|
||||
|
||||
throw new Error(offlineMessage ?? RETRY_WHEN_ONLINE_MESSAGE);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
|
||||
const fetchMock = vi.fn();
|
||||
const enqueueMock = vi.fn();
|
||||
|
||||
vi.mock("@bytelyst/api-client", () => ({
|
||||
createApiClient: () => ({
|
||||
@ -8,11 +9,25 @@ vi.mock("@bytelyst/api-client", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
import { getNoteDetail } from "@/lib/notes-client";
|
||||
vi.mock("@/lib/offline-queue", () => ({
|
||||
getOfflineQueue: () => ({ enqueue: enqueueMock }),
|
||||
}));
|
||||
|
||||
import { createNote, getNoteDetail, updateNoteDetail } from "@/lib/notes-client";
|
||||
import { OFFLINE_QUEUE_MESSAGE } from "@/lib/mutation-retry";
|
||||
|
||||
function setOnline(value: boolean) {
|
||||
Object.defineProperty(navigator, "onLine", {
|
||||
configurable: true,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
describe("getNoteDetail", () => {
|
||||
beforeEach(() => {
|
||||
fetchMock.mockReset();
|
||||
enqueueMock.mockClear();
|
||||
setOnline(true);
|
||||
});
|
||||
|
||||
it("returns persisted tasks only, preserves artifact blob metadata, and normalizes review state", async () => {
|
||||
@ -131,3 +146,41 @@ describe("getNoteDetail", () => {
|
||||
expect(note).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("retryable note mutations", () => {
|
||||
beforeEach(() => {
|
||||
fetchMock.mockReset();
|
||||
enqueueMock.mockClear();
|
||||
setOnline(true);
|
||||
});
|
||||
|
||||
it("queues note updates when the browser is offline", async () => {
|
||||
setOnline(false);
|
||||
fetchMock.mockRejectedValue(new Error("Failed to fetch"));
|
||||
|
||||
await expect(updateNoteDetail("note-1", "ws-1", { title: "Offline edit" }))
|
||||
.rejects.toThrow(OFFLINE_QUEUE_MESSAGE);
|
||||
|
||||
expect(enqueueMock).toHaveBeenCalledWith({
|
||||
id: "note-1",
|
||||
action: "patch",
|
||||
path: "/notes/note-1?workspaceId=ws-1",
|
||||
payload: { title: "Offline edit" },
|
||||
});
|
||||
});
|
||||
|
||||
it("queues note creation when the browser is offline", async () => {
|
||||
setOnline(false);
|
||||
fetchMock.mockRejectedValue(new Error("Failed to fetch"));
|
||||
|
||||
const input = { id: "note-new", workspaceId: "ws-1", title: "New", body: "Body" };
|
||||
await expect(createNote(input)).rejects.toThrow(OFFLINE_QUEUE_MESSAGE);
|
||||
|
||||
expect(enqueueMock).toHaveBeenCalledWith({
|
||||
id: "note-new",
|
||||
action: "post",
|
||||
path: "/notes",
|
||||
payload: input,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { createNotesApiClient } from "@/lib/api-helpers";
|
||||
import { withMutationRetry } from "@/lib/mutation-retry";
|
||||
import type {
|
||||
AgentTimelineItem,
|
||||
ArtifactSummary,
|
||||
@ -264,13 +265,14 @@ export async function updateNoteDetail(
|
||||
},
|
||||
): Promise<void> {
|
||||
const api = createNotesApiClient();
|
||||
await api.fetch(
|
||||
`/notes/${encodeURIComponent(noteId)}?workspaceId=${encodeURIComponent(workspaceId)}`,
|
||||
{
|
||||
const path = `/notes/${encodeURIComponent(noteId)}?workspaceId=${encodeURIComponent(workspaceId)}`;
|
||||
await withMutationRetry({
|
||||
run: () => api.fetch(path, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(updates),
|
||||
},
|
||||
);
|
||||
}),
|
||||
queue: { id: noteId, action: "patch", path, payload: updates },
|
||||
});
|
||||
}
|
||||
|
||||
export async function createNoteArtifact(input: {
|
||||
@ -285,9 +287,12 @@ export async function createNoteArtifact(input: {
|
||||
sizeBytes?: number;
|
||||
}): Promise<void> {
|
||||
const api = createNotesApiClient();
|
||||
await api.fetch("/note-artifacts", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
await withMutationRetry({
|
||||
run: () => api.fetch("/note-artifacts", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
}),
|
||||
queue: { id: input.id, action: "post", path: "/note-artifacts", payload: input },
|
||||
});
|
||||
}
|
||||
|
||||
@ -301,9 +306,12 @@ export async function createNoteTask(input: {
|
||||
source?: "manual" | "extracted";
|
||||
}): Promise<void> {
|
||||
const api = createNotesApiClient();
|
||||
await api.fetch("/note-tasks", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
await withMutationRetry({
|
||||
run: () => api.fetch("/note-tasks", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
}),
|
||||
queue: { id: input.id, action: "post", path: "/note-tasks", payload: input },
|
||||
});
|
||||
}
|
||||
|
||||
@ -391,25 +399,36 @@ export async function createNote(input: {
|
||||
sourceType?: string;
|
||||
}): Promise<NoteDoc> {
|
||||
const api = createNotesApiClient();
|
||||
return api.fetch<NoteDoc>("/notes", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
return withMutationRetry({
|
||||
run: () => api.fetch<NoteDoc>("/notes", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
}),
|
||||
queue: { id: input.id, action: "post", path: "/notes", payload: input },
|
||||
});
|
||||
}
|
||||
|
||||
export async function archiveNote(noteId: string, workspaceId: string): Promise<void> {
|
||||
const api = createNotesApiClient();
|
||||
await api.fetch(`/notes/${encodeURIComponent(noteId)}/archive`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ workspaceId }),
|
||||
const path = `/notes/${encodeURIComponent(noteId)}/archive`;
|
||||
await withMutationRetry({
|
||||
run: () => api.fetch(path, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ workspaceId }),
|
||||
}),
|
||||
queue: { id: `${noteId}:archive`, action: "post", path, payload: { workspaceId } },
|
||||
});
|
||||
}
|
||||
|
||||
export async function restoreNote(noteId: string, workspaceId: string): Promise<void> {
|
||||
const api = createNotesApiClient();
|
||||
await api.fetch(`/notes/${encodeURIComponent(noteId)}/restore`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ workspaceId }),
|
||||
const path = `/notes/${encodeURIComponent(noteId)}/restore`;
|
||||
await withMutationRetry({
|
||||
run: () => api.fetch(path, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ workspaceId }),
|
||||
}),
|
||||
queue: { id: `${noteId}:restore`, action: "post", path, payload: { workspaceId } },
|
||||
});
|
||||
}
|
||||
|
||||
@ -435,9 +454,12 @@ export async function createWorkspace(input: {
|
||||
description?: string;
|
||||
}): Promise<WorkspaceDoc> {
|
||||
const api = createNotesApiClient();
|
||||
return api.fetch<WorkspaceDoc>("/workspaces", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
return withMutationRetry({
|
||||
run: () => api.fetch<WorkspaceDoc>("/workspaces", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
}),
|
||||
queue: { id: input.id, action: "post", path: "/workspaces", payload: input },
|
||||
});
|
||||
}
|
||||
|
||||
@ -446,16 +468,24 @@ export async function updateWorkspace(
|
||||
updates: { name?: string; description?: string },
|
||||
): Promise<void> {
|
||||
const api = createNotesApiClient();
|
||||
await api.fetch(`/workspaces/${encodeURIComponent(workspaceId)}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(updates),
|
||||
const path = `/workspaces/${encodeURIComponent(workspaceId)}`;
|
||||
await withMutationRetry({
|
||||
run: () => api.fetch(path, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(updates),
|
||||
}),
|
||||
queue: { id: workspaceId, action: "patch", path, payload: updates },
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteWorkspace(workspaceId: string): Promise<void> {
|
||||
const api = createNotesApiClient();
|
||||
await api.fetch(`/workspaces/${encodeURIComponent(workspaceId)}`, {
|
||||
method: "DELETE",
|
||||
const path = `/workspaces/${encodeURIComponent(workspaceId)}`;
|
||||
await withMutationRetry({
|
||||
run: () => api.fetch(path, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
queue: { id: workspaceId, action: "delete", path, payload: {} },
|
||||
});
|
||||
}
|
||||
|
||||
@ -467,8 +497,11 @@ export async function createNoteRelationship(input: {
|
||||
relationshipType: string;
|
||||
}): Promise<void> {
|
||||
const api = createNotesApiClient();
|
||||
await api.fetch("/note-relationships", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
await withMutationRetry({
|
||||
run: () => api.fetch("/note-relationships", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
}),
|
||||
queue: { id: input.id, action: "post", path: "/note-relationships", payload: input },
|
||||
});
|
||||
}
|
||||
|
||||
@ -6,7 +6,9 @@ import { createNotesApiClient } from "@/lib/api-helpers";
|
||||
|
||||
const ACTION_METHOD: Record<string, string> = {
|
||||
create: "POST",
|
||||
post: "POST",
|
||||
update: "PATCH",
|
||||
patch: "PATCH",
|
||||
delete: "DELETE",
|
||||
};
|
||||
|
||||
|
||||
@ -23,10 +23,19 @@ import {
|
||||
getKnowledgeGaps,
|
||||
listPromptHistory,
|
||||
} from "@/lib/prompt-client";
|
||||
import { RETRY_WHEN_ONLINE_MESSAGE } from "@/lib/mutation-retry";
|
||||
|
||||
function setOnline(value: boolean) {
|
||||
Object.defineProperty(navigator, "onLine", {
|
||||
configurable: true,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
describe("prompt-client", () => {
|
||||
beforeEach(() => {
|
||||
fetchMock.mockReset();
|
||||
setOnline(true);
|
||||
});
|
||||
|
||||
it("listPromptTemplates returns items array", async () => {
|
||||
@ -68,6 +77,14 @@ describe("prompt-client", () => {
|
||||
expect(result.content).toBe("Summary");
|
||||
});
|
||||
|
||||
it("runPrompt gives clear retry UX when offline", async () => {
|
||||
setOnline(false);
|
||||
fetchMock.mockRejectedValue(new Error("Failed to fetch"));
|
||||
|
||||
await expect(runPrompt({ templateId: "summarize", noteId: "n1", workspaceId: "ws1" }))
|
||||
.rejects.toThrow(RETRY_WHEN_ONLINE_MESSAGE);
|
||||
});
|
||||
|
||||
it("suggestTags returns tag array", async () => {
|
||||
fetchMock.mockResolvedValue({ tags: ["tag1", "tag2"] });
|
||||
const tags = await suggestTags("n1", "ws1");
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { createNotesApiClient, getAccessToken } from "@/lib/api-helpers";
|
||||
import { RETRY_WHEN_ONLINE_MESSAGE, withMutationRetry } from "@/lib/mutation-retry";
|
||||
import type {
|
||||
PromptTemplate,
|
||||
RunPromptInput,
|
||||
@ -26,24 +27,34 @@ export async function createPromptTemplate(
|
||||
input: Omit<PromptTemplate, "id" | "isBuiltin">,
|
||||
): Promise<PromptTemplate> {
|
||||
const api = createNotesApiClient();
|
||||
return api.fetch<PromptTemplate>("/note-prompts", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
return withMutationRetry({
|
||||
run: () => api.fetch<PromptTemplate>("/note-prompts", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
}),
|
||||
queue: { id: input.slug, action: "post", path: "/note-prompts", payload: input },
|
||||
});
|
||||
}
|
||||
|
||||
export async function deletePromptTemplate(id: string): Promise<void> {
|
||||
const api = createNotesApiClient();
|
||||
await api.fetch(`/note-prompts/${encodeURIComponent(id)}`, { method: "DELETE" });
|
||||
const path = `/note-prompts/${encodeURIComponent(id)}`;
|
||||
await withMutationRetry({
|
||||
run: () => api.fetch(path, { method: "DELETE" }),
|
||||
queue: { id, action: "delete", path, payload: {} },
|
||||
});
|
||||
}
|
||||
|
||||
// ── Run prompts ───────────────────────────────────────────────
|
||||
|
||||
export async function runPrompt(input: RunPromptInput): Promise<RunPromptOutput> {
|
||||
const api = createNotesApiClient();
|
||||
return api.fetch<RunPromptOutput>("/note-prompts/run", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
return withMutationRetry({
|
||||
run: () => api.fetch<RunPromptOutput>("/note-prompts/run", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
}),
|
||||
offlineMessage: RETRY_WHEN_ONLINE_MESSAGE,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { createNotesApiClient } from "@/lib/api-helpers";
|
||||
import { RETRY_WHEN_ONLINE_MESSAGE, withMutationRetry } from "@/lib/mutation-retry";
|
||||
import type { AgentTimelineItem, ApprovalQueueItem, NoteAgentActionDoc, NoteAgentActionListResponse } from "@/lib/types";
|
||||
|
||||
|
||||
@ -46,18 +47,19 @@ async function updateAgentActionState(
|
||||
reviewNote?: string,
|
||||
): Promise<NoteAgentActionDoc> {
|
||||
const api = createNotesApiClient();
|
||||
const path = `/note-agent-actions/${encodeURIComponent(id)}?workspaceId=${encodeURIComponent(workspaceId)}`;
|
||||
|
||||
return api.fetch<NoteAgentActionDoc>(
|
||||
`/note-agent-actions/${encodeURIComponent(id)}?workspaceId=${encodeURIComponent(workspaceId)}`,
|
||||
{
|
||||
return withMutationRetry({
|
||||
run: () => api.fetch<NoteAgentActionDoc>(path, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({
|
||||
state,
|
||||
reviewedAt: new Date().toISOString(),
|
||||
...(reviewNote ? { reviewNote } : {}),
|
||||
}),
|
||||
},
|
||||
);
|
||||
}),
|
||||
offlineMessage: RETRY_WHEN_ONLINE_MESSAGE,
|
||||
});
|
||||
}
|
||||
|
||||
export async function listApprovalQueue(): Promise<ApprovalQueueItem[]> {
|
||||
@ -93,16 +95,16 @@ export async function batchReviewItems(
|
||||
reviewNote?: string,
|
||||
): Promise<{ updated: number; total: number }> {
|
||||
const api = createNotesApiClient();
|
||||
const result = await api.fetch<{ updated: number; total: number }>(
|
||||
"/note-agent-actions/batch-review",
|
||||
{
|
||||
const result = await withMutationRetry({
|
||||
run: () => api.fetch<{ updated: number; total: number }>("/note-agent-actions/batch-review", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
ids: items.map((item) => ({ id: item.id, workspaceId: item.workspaceId })),
|
||||
state,
|
||||
...(reviewNote ? { reviewNote } : {}),
|
||||
}),
|
||||
},
|
||||
);
|
||||
}),
|
||||
offlineMessage: RETRY_WHEN_ONLINE_MESSAGE,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user