@agent-native/dispatch 0.31.3 → 0.31.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +1 -1
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +32 -4
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/routes/pages/transactional-email.$appId.$id.d.ts.map +1 -1
- package/dist/routes/pages/transactional-email.$appId.$id.js.map +1 -1
- package/dist/routes/pages/transactional-email.js.map +1 -1
- package/dist/server/lib/mcp-gateway.d.ts.map +1 -1
- package/dist/server/lib/mcp-gateway.js +1 -1
- package/dist/server/lib/mcp-gateway.js.map +1 -1
- package/package.json +3 -3
- package/src/components/layout/Layout.tsx +1 -11
- package/src/components/workspace-app-card.spec.tsx +104 -8
- package/src/components/workspace-app-card.tsx +123 -68
- package/src/routes/pages/transactional-email.$appId.$id.tsx +1 -2
- package/src/routes/pages/transactional-email.tsx +1 -1
- package/src/server/lib/mcp-gateway.ts +1 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @vitest-environment happy-dom
|
|
2
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
2
3
|
import React, { act } from "react";
|
|
3
4
|
import { createRoot, type Root } from "react-dom/client";
|
|
4
5
|
import { MemoryRouter } from "react-router";
|
|
@@ -7,13 +8,29 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
7
8
|
import { TooltipProvider } from "./ui/tooltip";
|
|
8
9
|
import { WorkspaceAppCard } from "./workspace-app-card";
|
|
9
10
|
|
|
10
|
-
vi.mock("
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
vi.mock("../../../core/dist/client/use-action.js", () => ({
|
|
12
|
+
useActionMutation: () => ({
|
|
13
|
+
mutate: vi.fn(),
|
|
14
|
+
isPending: false,
|
|
15
|
+
}),
|
|
16
|
+
useActionQuery: () => ({
|
|
17
|
+
data: {
|
|
18
|
+
ownerEmail: "owner@example.com",
|
|
19
|
+
orgId: null,
|
|
20
|
+
visibility: "private",
|
|
21
|
+
role: "owner",
|
|
22
|
+
shares: [],
|
|
23
|
+
},
|
|
24
|
+
isLoading: false,
|
|
25
|
+
isError: false,
|
|
26
|
+
error: null,
|
|
27
|
+
refetch: vi.fn(async () => undefined),
|
|
28
|
+
}),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
vi.mock("../../../core/dist/client/i18n.js", () => ({
|
|
32
|
+
useT: () => (key: string, values?: Record<string, unknown>) =>
|
|
33
|
+
String(values?.defaultValue ?? key),
|
|
17
34
|
}));
|
|
18
35
|
|
|
19
36
|
vi.mock("@agent-native/core/client/hooks", () => ({
|
|
@@ -44,6 +61,7 @@ vi.mock("@agent-native/core/client/host", () => ({
|
|
|
44
61
|
describe("WorkspaceAppCard", () => {
|
|
45
62
|
let container: HTMLDivElement;
|
|
46
63
|
let root: Root;
|
|
64
|
+
let queryClient: QueryClient;
|
|
47
65
|
|
|
48
66
|
beforeEach(() => {
|
|
49
67
|
frameState.inBuilderFrame = false;
|
|
@@ -51,10 +69,17 @@ describe("WorkspaceAppCard", () => {
|
|
|
51
69
|
container = document.createElement("div");
|
|
52
70
|
document.body.appendChild(container);
|
|
53
71
|
root = createRoot(container);
|
|
72
|
+
queryClient = new QueryClient({
|
|
73
|
+
defaultOptions: {
|
|
74
|
+
queries: { retry: false },
|
|
75
|
+
mutations: { retry: false },
|
|
76
|
+
},
|
|
77
|
+
});
|
|
54
78
|
});
|
|
55
79
|
|
|
56
80
|
afterEach(() => {
|
|
57
81
|
act(() => root.unmount());
|
|
82
|
+
queryClient.clear();
|
|
58
83
|
container.remove();
|
|
59
84
|
vi.unstubAllGlobals();
|
|
60
85
|
});
|
|
@@ -193,6 +218,77 @@ describe("WorkspaceAppCard", () => {
|
|
|
193
218
|
}
|
|
194
219
|
});
|
|
195
220
|
|
|
221
|
+
it("opens Share from the app settings menu instead of the card actions", async () => {
|
|
222
|
+
const animationFrameCallbacks: FrameRequestCallback[] = [];
|
|
223
|
+
vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => {
|
|
224
|
+
animationFrameCallbacks.push(callback);
|
|
225
|
+
return animationFrameCallbacks.length;
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
await act(async () => {
|
|
229
|
+
root.render(
|
|
230
|
+
<QueryClientProvider client={queryClient}>
|
|
231
|
+
<MemoryRouter>
|
|
232
|
+
<TooltipProvider>
|
|
233
|
+
<WorkspaceAppCard
|
|
234
|
+
app={{
|
|
235
|
+
id: "analytics",
|
|
236
|
+
name: "Analytics",
|
|
237
|
+
path: "/analytics",
|
|
238
|
+
status: "ready",
|
|
239
|
+
}}
|
|
240
|
+
/>
|
|
241
|
+
</TooltipProvider>
|
|
242
|
+
</MemoryRouter>
|
|
243
|
+
</QueryClientProvider>,
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(container.querySelector('button[aria-label="Share"]')).toBeNull();
|
|
248
|
+
|
|
249
|
+
const settingsButton = container.querySelector<HTMLButtonElement>(
|
|
250
|
+
'button[aria-label="Settings for Analytics"]',
|
|
251
|
+
);
|
|
252
|
+
expect(settingsButton).not.toBeNull();
|
|
253
|
+
|
|
254
|
+
await act(async () => {
|
|
255
|
+
settingsButton?.dispatchEvent(
|
|
256
|
+
new MouseEvent("pointerdown", { bubbles: true, button: 0 }),
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const shareItem = Array.from(
|
|
261
|
+
document.querySelectorAll<HTMLElement>('[role="menuitem"]'),
|
|
262
|
+
).find((item) => item.textContent?.trim() === "Share");
|
|
263
|
+
expect(shareItem).not.toBeUndefined();
|
|
264
|
+
|
|
265
|
+
await act(async () => shareItem?.click());
|
|
266
|
+
|
|
267
|
+
expect(container.querySelector('button[aria-label="Share"]')).toBeNull();
|
|
268
|
+
expect(document.querySelector('[role="menu"]')).toBeNull();
|
|
269
|
+
expect(animationFrameCallbacks).toHaveLength(1);
|
|
270
|
+
|
|
271
|
+
await act(async () => animationFrameCallbacks.shift()?.(0));
|
|
272
|
+
|
|
273
|
+
expect(
|
|
274
|
+
container.querySelector('button[aria-label="Share"]'),
|
|
275
|
+
).not.toBeNull();
|
|
276
|
+
expect(
|
|
277
|
+
document.body.querySelector("[data-agent-native-share-overlay]"),
|
|
278
|
+
).not.toBeNull();
|
|
279
|
+
|
|
280
|
+
const shareButton = container.querySelector<HTMLButtonElement>(
|
|
281
|
+
'button[aria-label="Share"]',
|
|
282
|
+
);
|
|
283
|
+
expect(shareButton).not.toBeNull();
|
|
284
|
+
|
|
285
|
+
await act(async () => shareButton?.click());
|
|
286
|
+
expect(animationFrameCallbacks).toHaveLength(1);
|
|
287
|
+
|
|
288
|
+
await act(async () => animationFrameCallbacks.shift()?.(0));
|
|
289
|
+
expect(document.activeElement).toBe(settingsButton);
|
|
290
|
+
});
|
|
291
|
+
|
|
196
292
|
it("keeps pinning in the app open menu", async () => {
|
|
197
293
|
const onTogglePinned = vi.fn();
|
|
198
294
|
await act(async () => {
|
|
@@ -270,7 +366,7 @@ describe("WorkspaceAppCard", () => {
|
|
|
270
366
|
expect(settingsMenu?.className).toContain("w-48");
|
|
271
367
|
expect(settingsMenu?.className).toContain("bg-popover");
|
|
272
368
|
expect(settingsMenu?.className).toContain("shadow-md");
|
|
273
|
-
expect(document.querySelectorAll('[role="menuitem"] svg').length).toBe(
|
|
369
|
+
expect(document.querySelectorAll('[role="menuitem"] svg').length).toBe(5);
|
|
274
370
|
|
|
275
371
|
await act(async () => resourcesItem?.click());
|
|
276
372
|
|
|
@@ -15,13 +15,14 @@ import {
|
|
|
15
15
|
IconFileText,
|
|
16
16
|
IconKey,
|
|
17
17
|
IconPin,
|
|
18
|
+
IconShare3,
|
|
18
19
|
IconSettings,
|
|
19
20
|
IconTrash,
|
|
20
21
|
IconUser,
|
|
21
22
|
IconUsersGroup,
|
|
22
23
|
IconWorld,
|
|
23
24
|
} from "@tabler/icons-react";
|
|
24
|
-
import { useEffect, useState, type FormEvent } from "react";
|
|
25
|
+
import { useEffect, useRef, useState, type FormEvent } from "react";
|
|
25
26
|
import { useNavigate } from "react-router";
|
|
26
27
|
import { toast } from "sonner";
|
|
27
28
|
|
|
@@ -67,6 +68,23 @@ import { AppResourceEffectiveStack } from "./workspace-resource-effective-stack"
|
|
|
67
68
|
const APP_CARD_ACTION_CLASS =
|
|
68
69
|
"size-7 rounded-md p-0 text-muted-foreground transition-[background-color,color] hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-foreground";
|
|
69
70
|
|
|
71
|
+
function deferWorkspaceAppOverlayOpen(
|
|
72
|
+
event: { preventDefault: () => void },
|
|
73
|
+
closeMenu: () => void,
|
|
74
|
+
openOverlay: () => void,
|
|
75
|
+
): void {
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
closeMenu();
|
|
78
|
+
if (
|
|
79
|
+
typeof window !== "undefined" &&
|
|
80
|
+
typeof window.requestAnimationFrame === "function"
|
|
81
|
+
) {
|
|
82
|
+
window.requestAnimationFrame(() => openOverlay());
|
|
83
|
+
} else {
|
|
84
|
+
setTimeout(openOverlay, 0);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
70
88
|
export function WorkspaceAppCard({
|
|
71
89
|
app,
|
|
72
90
|
className,
|
|
@@ -211,17 +229,6 @@ export function WorkspaceAppCard({
|
|
|
211
229
|
pendingOpenLabel={pendingOpenLabel}
|
|
212
230
|
onTogglePinned={onTogglePinned}
|
|
213
231
|
/>
|
|
214
|
-
{!isPending ? (
|
|
215
|
-
<div className="pointer-events-auto">
|
|
216
|
-
<ShareButton
|
|
217
|
-
resourceType="workspace-app"
|
|
218
|
-
resourceId={app.id}
|
|
219
|
-
resourceTitle={app.name}
|
|
220
|
-
trigger="icon"
|
|
221
|
-
triggerClassName={APP_CARD_ACTION_CLASS}
|
|
222
|
-
/>
|
|
223
|
-
</div>
|
|
224
|
-
) : null}
|
|
225
232
|
<WorkspaceAppSettings
|
|
226
233
|
app={app}
|
|
227
234
|
isArchived={isArchived}
|
|
@@ -236,6 +243,7 @@ export function WorkspaceAppCard({
|
|
|
236
243
|
createdBy: t("agents.dashboardMetadataCreatedBy"),
|
|
237
244
|
owner: t("dispatch.pages.appMetadataOwner"),
|
|
238
245
|
teams: t("dispatch.pages.appMetadataTeams"),
|
|
246
|
+
share: t("share.share", { defaultValue: "Share" }),
|
|
239
247
|
}}
|
|
240
248
|
/>
|
|
241
249
|
</div>
|
|
@@ -376,6 +384,7 @@ type WorkspaceAppMetadataLabels = {
|
|
|
376
384
|
createdBy: string;
|
|
377
385
|
owner: string;
|
|
378
386
|
teams: string;
|
|
387
|
+
share: string;
|
|
379
388
|
};
|
|
380
389
|
|
|
381
390
|
function WorkspaceAppSettings({
|
|
@@ -401,66 +410,112 @@ function WorkspaceAppSettings({
|
|
|
401
410
|
onRemovePending: () => void;
|
|
402
411
|
labels: WorkspaceAppMetadataLabels;
|
|
403
412
|
}) {
|
|
413
|
+
const [shareOpen, setShareOpen] = useState(false);
|
|
414
|
+
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
415
|
+
const settingsTriggerRef = useRef<HTMLButtonElement>(null);
|
|
416
|
+
|
|
417
|
+
const handleShareOpenChange = (open: boolean) => {
|
|
418
|
+
setShareOpen(open);
|
|
419
|
+
if (open) return;
|
|
420
|
+
|
|
421
|
+
const restoreSettingsFocus = () => settingsTriggerRef.current?.focus();
|
|
422
|
+
if (
|
|
423
|
+
typeof window !== "undefined" &&
|
|
424
|
+
typeof window.requestAnimationFrame === "function"
|
|
425
|
+
) {
|
|
426
|
+
window.requestAnimationFrame(restoreSettingsFocus);
|
|
427
|
+
} else {
|
|
428
|
+
setTimeout(restoreSettingsFocus, 0);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
404
432
|
return (
|
|
405
|
-
|
|
406
|
-
<
|
|
407
|
-
<
|
|
408
|
-
<
|
|
409
|
-
<
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
433
|
+
<>
|
|
434
|
+
<DropdownMenu open={settingsOpen} onOpenChange={setSettingsOpen}>
|
|
435
|
+
<Tooltip>
|
|
436
|
+
<TooltipTrigger asChild>
|
|
437
|
+
<DropdownMenuTrigger asChild>
|
|
438
|
+
<Button
|
|
439
|
+
type="button"
|
|
440
|
+
variant="ghost"
|
|
441
|
+
size="sm"
|
|
442
|
+
ref={settingsTriggerRef}
|
|
443
|
+
aria-label={`Settings for ${app.name}`}
|
|
444
|
+
className={APP_CARD_ACTION_CLASS}
|
|
445
|
+
>
|
|
446
|
+
<IconSettings size={15} />
|
|
447
|
+
</Button>
|
|
448
|
+
</DropdownMenuTrigger>
|
|
449
|
+
</TooltipTrigger>
|
|
450
|
+
<TooltipContent>App settings</TooltipContent>
|
|
451
|
+
</Tooltip>
|
|
452
|
+
<DropdownMenuContent
|
|
453
|
+
align="end"
|
|
454
|
+
className={cn(APP_ACTION_MENU_CONTENT_CLASS, "min-w-max")}
|
|
455
|
+
>
|
|
456
|
+
<DropdownMenuItem onSelect={onEdit}>
|
|
457
|
+
<IconEdit size={14} aria-hidden="true" />
|
|
458
|
+
Edit details
|
|
459
|
+
</DropdownMenuItem>
|
|
460
|
+
{!isPending ? (
|
|
461
|
+
<DropdownMenuItem
|
|
462
|
+
onSelect={(event) =>
|
|
463
|
+
deferWorkspaceAppOverlayOpen(
|
|
464
|
+
event,
|
|
465
|
+
() => setSettingsOpen(false),
|
|
466
|
+
() => setShareOpen(true),
|
|
467
|
+
)
|
|
468
|
+
}
|
|
415
469
|
>
|
|
416
|
-
<
|
|
417
|
-
|
|
418
|
-
</DropdownMenuTrigger>
|
|
419
|
-
</TooltipTrigger>
|
|
420
|
-
<TooltipContent>App settings</TooltipContent>
|
|
421
|
-
</Tooltip>
|
|
422
|
-
<DropdownMenuContent
|
|
423
|
-
align="end"
|
|
424
|
-
className={cn(APP_ACTION_MENU_CONTENT_CLASS, "min-w-max")}
|
|
425
|
-
>
|
|
426
|
-
<DropdownMenuItem onSelect={onEdit}>
|
|
427
|
-
<IconEdit size={14} aria-hidden="true" />
|
|
428
|
-
Edit details
|
|
429
|
-
</DropdownMenuItem>
|
|
430
|
-
{!isPending && !isArchived ? (
|
|
431
|
-
<>
|
|
432
|
-
<DropdownMenuItem onSelect={onKeys}>
|
|
433
|
-
<IconKey size={14} aria-hidden="true" />
|
|
434
|
-
Manage keys
|
|
470
|
+
<IconShare3 size={14} aria-hidden="true" />
|
|
471
|
+
{labels.share}
|
|
435
472
|
</DropdownMenuItem>
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
473
|
+
) : null}
|
|
474
|
+
{!isPending && !isArchived ? (
|
|
475
|
+
<>
|
|
476
|
+
<DropdownMenuItem onSelect={onKeys}>
|
|
477
|
+
<IconKey size={14} aria-hidden="true" />
|
|
478
|
+
Manage keys
|
|
479
|
+
</DropdownMenuItem>
|
|
480
|
+
<DropdownMenuItem onSelect={onResources}>
|
|
481
|
+
<IconFileText size={14} aria-hidden="true" />
|
|
482
|
+
Agent resources
|
|
483
|
+
</DropdownMenuItem>
|
|
484
|
+
</>
|
|
485
|
+
) : null}
|
|
486
|
+
{isPending ? (
|
|
487
|
+
<DropdownMenuItem
|
|
488
|
+
onSelect={onRemovePending}
|
|
489
|
+
className="text-destructive focus:text-destructive"
|
|
490
|
+
>
|
|
491
|
+
<IconTrash size={14} aria-hidden="true" />
|
|
492
|
+
Remove from list
|
|
439
493
|
</DropdownMenuItem>
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
<
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
494
|
+
) : isArchived ? (
|
|
495
|
+
<DropdownMenuItem onSelect={onUnarchive}>
|
|
496
|
+
<IconEye size={14} aria-hidden="true" />
|
|
497
|
+
Restore to list
|
|
498
|
+
</DropdownMenuItem>
|
|
499
|
+
) : (
|
|
500
|
+
<DropdownMenuItem onSelect={onArchive}>
|
|
501
|
+
<IconEyeOff size={14} aria-hidden="true" />
|
|
502
|
+
Hide from list
|
|
503
|
+
</DropdownMenuItem>
|
|
504
|
+
)}
|
|
505
|
+
<WorkspaceAppMetadata app={app} labels={labels} />
|
|
506
|
+
</DropdownMenuContent>
|
|
507
|
+
</DropdownMenu>
|
|
508
|
+
{!isPending && shareOpen ? (
|
|
509
|
+
<ShareButton
|
|
510
|
+
resourceType="workspace-app"
|
|
511
|
+
resourceId={app.id}
|
|
512
|
+
resourceTitle={app.name}
|
|
513
|
+
defaultOpen
|
|
514
|
+
onOpenChange={handleShareOpenChange}
|
|
515
|
+
triggerClassName="sr-only"
|
|
516
|
+
/>
|
|
517
|
+
) : null}
|
|
518
|
+
</>
|
|
464
519
|
);
|
|
465
520
|
}
|
|
466
521
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
2
2
|
import { useT } from "@agent-native/core/client/i18n";
|
|
3
3
|
import { IconAlertTriangle, IconArrowLeft } from "@tabler/icons-react";
|
|
4
4
|
import { useQueries, useQuery } from "@tanstack/react-query";
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
callAppAction,
|
|
11
11
|
fetchAppEmailCatalog,
|
|
12
12
|
type AppEmailCatalog,
|
|
13
|
-
type AppTransactionalEmail,
|
|
14
13
|
type LocalTransactionalEmailCatalog,
|
|
15
14
|
} from "../../client/transactional-emails";
|
|
16
15
|
import { ActionQueryError } from "../../components/action-query-error";
|
|
@@ -31,10 +31,7 @@ import {
|
|
|
31
31
|
DISPATCH_WORKSPACE_SSO_FLAG,
|
|
32
32
|
isWorkspaceSsoAppUrl,
|
|
33
33
|
} from "../../shared/workspace-sso.js";
|
|
34
|
-
import {
|
|
35
|
-
listWorkspaceApps,
|
|
36
|
-
type WorkspaceAppSummary,
|
|
37
|
-
} from "./app-creation-store.js";
|
|
34
|
+
import { listWorkspaceApps } from "./app-creation-store.js";
|
|
38
35
|
import {
|
|
39
36
|
getDispatchMcpAppAccessSettings,
|
|
40
37
|
isAppAllowedByMcpAccess,
|