@agent-native/dispatch 0.24.0 → 0.24.3
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/actions/provider-api-register.d.ts +12 -12
- package/dist/components/admin-navigation.d.ts.map +1 -1
- package/dist/components/admin-navigation.js +21 -5
- package/dist/components/admin-navigation.js.map +1 -1
- package/dist/components/agents-panel.d.ts.map +1 -1
- package/dist/components/agents-panel.js +7 -2
- package/dist/components/agents-panel.js.map +1 -1
- package/dist/components/app-list-row.d.ts +1 -1
- package/dist/components/app-list-row.d.ts.map +1 -1
- package/dist/components/app-list-row.js +1 -1
- package/dist/components/app-list-row.js.map +1 -1
- package/dist/components/app-open-actions.d.ts +1 -17
- package/dist/components/app-open-actions.d.ts.map +1 -1
- package/dist/components/app-open-actions.js +1 -20
- package/dist/components/app-open-actions.js.map +1 -1
- package/dist/components/app-row-settings.d.ts +6 -0
- package/dist/components/app-row-settings.d.ts.map +1 -0
- package/dist/components/app-row-settings.js +11 -0
- package/dist/components/app-row-settings.js.map +1 -0
- package/dist/components/automation-run-history-dialog.d.ts.map +1 -1
- package/dist/components/automation-run-history-dialog.js +3 -2
- package/dist/components/automation-run-history-dialog.js.map +1 -1
- package/dist/components/connected-app-card.d.ts.map +1 -1
- package/dist/components/connected-app-card.js +1 -1
- package/dist/components/connected-app-card.js.map +1 -1
- package/dist/components/create-app-popover.js +2 -2
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +23 -7
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/dispatch-shell.d.ts.map +1 -1
- package/dist/components/dispatch-shell.js +5 -1
- package/dist/components/dispatch-shell.js.map +1 -1
- package/dist/components/docs-link.d.ts +16 -0
- package/dist/components/docs-link.d.ts.map +1 -0
- package/dist/components/docs-link.js +34 -0
- package/dist/components/docs-link.js.map +1 -0
- package/dist/components/layout/Layout.d.ts +1 -0
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +37 -28
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/messaging-setup-panel.d.ts.map +1 -1
- package/dist/components/messaging-setup-panel.js +3 -2
- package/dist/components/messaging-setup-panel.js.map +1 -1
- package/dist/components/usage-alerts-panel.d.ts +11 -0
- package/dist/components/usage-alerts-panel.d.ts.map +1 -0
- package/dist/components/usage-alerts-panel.js +125 -0
- package/dist/components/usage-alerts-panel.js.map +1 -0
- package/dist/components/workspace-app-card.d.ts +3 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +70 -29
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-app-search.d.ts +10 -0
- package/dist/components/workspace-app-search.d.ts.map +1 -0
- package/dist/components/workspace-app-search.js +27 -0
- package/dist/components/workspace-app-search.js.map +1 -0
- package/dist/components/workspace-resource-effective-stack.d.ts.map +1 -1
- package/dist/components/workspace-resource-effective-stack.js +3 -2
- package/dist/components/workspace-resource-effective-stack.js.map +1 -1
- package/dist/components/workspace-template-card.d.ts.map +1 -1
- package/dist/components/workspace-template-card.js +2 -1
- package/dist/components/workspace-template-card.js.map +1 -1
- package/dist/lib/workspace-app-layout.d.ts +19 -0
- package/dist/lib/workspace-app-layout.d.ts.map +1 -0
- package/dist/lib/workspace-app-layout.js +75 -0
- package/dist/lib/workspace-app-layout.js.map +1 -0
- package/dist/lib/workspace-apps.d.ts +10 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +59 -0
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/lib/workspace-apps.test.js +18 -1
- package/dist/lib/workspace-apps.test.js.map +1 -1
- package/dist/routes/pages/$appId.d.ts.map +1 -1
- package/dist/routes/pages/$appId.js +4 -2
- package/dist/routes/pages/$appId.js.map +1 -1
- package/dist/routes/pages/agents.d.ts.map +1 -1
- package/dist/routes/pages/agents.js +2 -3
- package/dist/routes/pages/agents.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +8 -3
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +31 -16
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/routes/pages/audit.d.ts.map +1 -1
- package/dist/routes/pages/audit.js +2 -1
- package/dist/routes/pages/audit.js.map +1 -1
- package/dist/routes/pages/dreams.d.ts.map +1 -1
- package/dist/routes/pages/dreams.js +14 -18
- package/dist/routes/pages/dreams.js.map +1 -1
- package/dist/routes/pages/integrations.d.ts.map +1 -1
- package/dist/routes/pages/integrations.js +51 -14
- package/dist/routes/pages/integrations.js.map +1 -1
- package/dist/routes/pages/metrics.d.ts.map +1 -1
- package/dist/routes/pages/metrics.js +4 -1
- package/dist/routes/pages/metrics.js.map +1 -1
- package/dist/routes/pages/operations.js +1 -1
- package/dist/routes/pages/operations.js.map +1 -1
- package/dist/routes/pages/settings.d.ts.map +1 -1
- package/dist/routes/pages/settings.js +7 -2
- package/dist/routes/pages/settings.js.map +1 -1
- package/dist/routes/pages/thread-debug.d.ts.map +1 -1
- package/dist/routes/pages/thread-debug.js +5 -8
- package/dist/routes/pages/thread-debug.js.map +1 -1
- package/dist/routes/pages/vault.d.ts.map +1 -1
- package/dist/routes/pages/vault.js +1 -1
- package/dist/routes/pages/vault.js.map +1 -1
- package/dist/routes/pages/workspace.d.ts.map +1 -1
- package/dist/routes/pages/workspace.js +13 -10
- package/dist/routes/pages/workspace.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +12 -52
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/shared/app-roles.d.ts +9 -0
- package/dist/shared/app-roles.d.ts.map +1 -0
- package/dist/shared/app-roles.js +7 -0
- package/dist/shared/app-roles.js.map +1 -0
- package/package.json +4 -3
- package/src/components/admin-navigation.tsx +57 -11
- package/src/components/agents-panel.tsx +54 -29
- package/src/components/app-list-row.tsx +1 -2
- package/src/components/app-open-actions.tsx +5 -114
- package/src/components/app-row-settings.tsx +54 -0
- package/src/components/automation-run-history-dialog.tsx +8 -4
- package/src/components/connected-app-card.tsx +0 -1
- package/src/components/create-app-popover.spec.tsx +1 -1
- package/src/components/create-app-popover.tsx +2 -2
- package/src/components/dispatch-control-plane.spec.tsx +36 -2
- package/src/components/dispatch-control-plane.tsx +83 -27
- package/src/components/dispatch-shell.tsx +7 -1
- package/src/components/docs-link.tsx +52 -0
- package/src/components/layout/Layout.spec.tsx +10 -0
- package/src/components/layout/Layout.tsx +50 -23
- package/src/components/messaging-setup-panel.tsx +10 -3
- package/src/components/usage-alerts-panel.tsx +445 -0
- package/src/components/workspace-app-card.spec.tsx +130 -9
- package/src/components/workspace-app-card.tsx +128 -71
- package/src/components/workspace-app-search.tsx +96 -0
- package/src/components/workspace-resource-effective-stack.tsx +10 -5
- package/src/components/workspace-template-card.spec.tsx +52 -23
- package/src/components/workspace-template-card.tsx +7 -10
- package/src/lib/workspace-app-layout.spec.ts +47 -0
- package/src/lib/workspace-app-layout.ts +121 -0
- package/src/lib/workspace-apps.test.ts +45 -1
- package/src/lib/workspace-apps.ts +72 -0
- package/src/routes/pages/$appId.tsx +3 -6
- package/src/routes/pages/agents.tsx +6 -3
- package/src/routes/pages/apps.$appId.tsx +6 -6
- package/src/routes/pages/apps.tsx +116 -62
- package/src/routes/pages/audit.tsx +28 -8
- package/src/routes/pages/dreams.tsx +92 -142
- package/src/routes/pages/integrations.tsx +147 -68
- package/src/routes/pages/metrics.tsx +8 -0
- package/src/routes/pages/operations.tsx +2 -2
- package/src/routes/pages/settings.tsx +14 -9
- package/src/routes/pages/thread-debug.tsx +18 -36
- package/src/routes/pages/vault.tsx +7 -2
- package/src/routes/pages/workspace.tsx +92 -56
- package/src/server/index.ts +4 -0
- package/src/server/lib/app-creation-store.spec.ts +90 -43
- package/src/server/lib/app-creation-store.ts +15 -61
- package/src/shared/app-roles.ts +8 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { APP_ACTION_MENU_CONTENT_CLASS } from "@agent-native/core/client/chat-first";
|
|
1
2
|
import {
|
|
2
3
|
useActionMutation,
|
|
3
4
|
useActionQuery,
|
|
4
5
|
} from "@agent-native/core/client/hooks";
|
|
5
|
-
import {
|
|
6
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
6
7
|
import {
|
|
7
|
-
IconCalendar,
|
|
8
8
|
IconChevronDown,
|
|
9
9
|
IconChevronRight,
|
|
10
10
|
IconClockHour4,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
IconEyeOff,
|
|
14
14
|
IconFileText,
|
|
15
15
|
IconKey,
|
|
16
|
+
IconPin,
|
|
16
17
|
IconSettings,
|
|
17
18
|
IconTrash,
|
|
18
19
|
IconUser,
|
|
@@ -64,15 +65,21 @@ const APP_CARD_ACTION_CLASS =
|
|
|
64
65
|
export function WorkspaceAppCard({
|
|
65
66
|
app,
|
|
66
67
|
className,
|
|
68
|
+
isPinned = false,
|
|
69
|
+
onTogglePinned,
|
|
67
70
|
}: {
|
|
68
71
|
app: WorkspaceAppSummary;
|
|
69
72
|
className?: string;
|
|
73
|
+
isPinned?: boolean;
|
|
74
|
+
onTogglePinned?: () => void;
|
|
70
75
|
}) {
|
|
71
76
|
const t = useT();
|
|
72
|
-
const { formatDate } = useFormatters();
|
|
73
77
|
const href = workspaceAppHref(app);
|
|
74
78
|
const isPending = app.status === "pending";
|
|
75
79
|
const pendingLabel = app.statusLabel || "Builder branch";
|
|
80
|
+
const pendingOpenLabel = t("dispatch.pages.openBuilderBranch", {
|
|
81
|
+
defaultValue: "Open in Builder",
|
|
82
|
+
});
|
|
76
83
|
const isArchived = !!app.archived;
|
|
77
84
|
const audience = app.audience ?? "internal";
|
|
78
85
|
const [editOpen, setEditOpen] = useState(false);
|
|
@@ -135,6 +142,13 @@ export function WorkspaceAppCard({
|
|
|
135
142
|
description: draftDescription.trim(),
|
|
136
143
|
});
|
|
137
144
|
};
|
|
145
|
+
const pinLabel = t(
|
|
146
|
+
isPinned ? "dispatch.pages.unpinApp" : "dispatch.pages.pinApp",
|
|
147
|
+
{
|
|
148
|
+
name: app.name,
|
|
149
|
+
defaultValue: `${isPinned ? "Unpin" : "Pin"} ${app.name}`,
|
|
150
|
+
},
|
|
151
|
+
);
|
|
138
152
|
|
|
139
153
|
return (
|
|
140
154
|
<>
|
|
@@ -174,16 +188,19 @@ export function WorkspaceAppCard({
|
|
|
174
188
|
Public
|
|
175
189
|
</span>
|
|
176
190
|
) : null}
|
|
177
|
-
{isPending && app.branchName ? (
|
|
178
|
-
<span className="min-w-0 truncate">{app.branchName}</span>
|
|
179
|
-
) : null}
|
|
180
191
|
</div>
|
|
181
192
|
</div>
|
|
182
193
|
<div className="flex shrink-0 items-center gap-2">
|
|
183
|
-
<WorkspaceAppOpenActions
|
|
194
|
+
<WorkspaceAppOpenActions
|
|
195
|
+
app={app}
|
|
196
|
+
href={href}
|
|
197
|
+
isPinned={isPinned}
|
|
198
|
+
pinLabel={pinLabel}
|
|
199
|
+
pendingOpenLabel={pendingOpenLabel}
|
|
200
|
+
onTogglePinned={onTogglePinned}
|
|
201
|
+
/>
|
|
184
202
|
<WorkspaceAppSettings
|
|
185
203
|
app={app}
|
|
186
|
-
formatDate={formatDate}
|
|
187
204
|
isArchived={isArchived}
|
|
188
205
|
isPending={isPending}
|
|
189
206
|
onEdit={() => setEditOpen(true)}
|
|
@@ -193,11 +210,9 @@ export function WorkspaceAppCard({
|
|
|
193
210
|
onUnarchive={handleUnarchive}
|
|
194
211
|
onRemovePending={handleRemovePending}
|
|
195
212
|
labels={{
|
|
196
|
-
created: t("agents.dashboardMetadataCreated"),
|
|
197
213
|
createdBy: t("agents.dashboardMetadataCreatedBy"),
|
|
198
214
|
owner: t("dispatch.pages.appMetadataOwner"),
|
|
199
215
|
teams: t("dispatch.pages.appMetadataTeams"),
|
|
200
|
-
notTracked: t("agents.notTracked"),
|
|
201
216
|
}}
|
|
202
217
|
/>
|
|
203
218
|
</div>
|
|
@@ -270,12 +285,35 @@ export function WorkspaceAppCard({
|
|
|
270
285
|
function WorkspaceAppOpenActions({
|
|
271
286
|
app,
|
|
272
287
|
href,
|
|
288
|
+
isPinned,
|
|
289
|
+
pinLabel,
|
|
290
|
+
pendingOpenLabel,
|
|
291
|
+
onTogglePinned,
|
|
273
292
|
}: {
|
|
274
293
|
app: WorkspaceAppSummary;
|
|
275
294
|
href: string | null;
|
|
295
|
+
isPinned: boolean;
|
|
296
|
+
pinLabel: string;
|
|
297
|
+
pendingOpenLabel: string;
|
|
298
|
+
onTogglePinned?: () => void;
|
|
276
299
|
}) {
|
|
277
300
|
const navigate = useNavigate();
|
|
278
301
|
const appRoute = workspaceAppRoute(app.id);
|
|
302
|
+
const isPending = app.status === "pending";
|
|
303
|
+
|
|
304
|
+
if (isPending) {
|
|
305
|
+
return href ? (
|
|
306
|
+
<Button asChild size="sm" variant="outline">
|
|
307
|
+
<a href={href} target="_blank" rel="noreferrer">
|
|
308
|
+
{pendingOpenLabel}
|
|
309
|
+
</a>
|
|
310
|
+
</Button>
|
|
311
|
+
) : (
|
|
312
|
+
<Button size="sm" variant="outline" disabled>
|
|
313
|
+
{pendingOpenLabel}
|
|
314
|
+
</Button>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
279
317
|
|
|
280
318
|
if (!href) {
|
|
281
319
|
return (
|
|
@@ -289,27 +327,34 @@ function WorkspaceAppOpenActions({
|
|
|
289
327
|
<AppOpenActions
|
|
290
328
|
name={app.name}
|
|
291
329
|
href={href}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
showInlineOption
|
|
295
|
-
onOpenInline={() => {
|
|
330
|
+
showNewTabOption
|
|
331
|
+
onOpen={() => {
|
|
296
332
|
navigate(appRoute);
|
|
297
333
|
}}
|
|
334
|
+
menuItems={
|
|
335
|
+
onTogglePinned
|
|
336
|
+
? [
|
|
337
|
+
{
|
|
338
|
+
id: "pin",
|
|
339
|
+
label: pinLabel,
|
|
340
|
+
icon: <IconPin size={14} strokeWidth={isPinned ? 2.2 : 1.7} />,
|
|
341
|
+
onSelect: onTogglePinned,
|
|
342
|
+
},
|
|
343
|
+
]
|
|
344
|
+
: undefined
|
|
345
|
+
}
|
|
298
346
|
/>
|
|
299
347
|
);
|
|
300
348
|
}
|
|
301
349
|
|
|
302
350
|
type WorkspaceAppMetadataLabels = {
|
|
303
|
-
created: string;
|
|
304
351
|
createdBy: string;
|
|
305
352
|
owner: string;
|
|
306
353
|
teams: string;
|
|
307
|
-
notTracked: string;
|
|
308
354
|
};
|
|
309
355
|
|
|
310
356
|
function WorkspaceAppSettings({
|
|
311
357
|
app,
|
|
312
|
-
formatDate,
|
|
313
358
|
isArchived,
|
|
314
359
|
isPending,
|
|
315
360
|
onEdit,
|
|
@@ -321,7 +366,6 @@ function WorkspaceAppSettings({
|
|
|
321
366
|
labels,
|
|
322
367
|
}: {
|
|
323
368
|
app: WorkspaceAppSummary;
|
|
324
|
-
formatDate: ReturnType<typeof useFormatters>["formatDate"];
|
|
325
369
|
isArchived: boolean;
|
|
326
370
|
isPending: boolean;
|
|
327
371
|
onEdit: () => void;
|
|
@@ -350,19 +394,22 @@ function WorkspaceAppSettings({
|
|
|
350
394
|
</TooltipTrigger>
|
|
351
395
|
<TooltipContent>App settings</TooltipContent>
|
|
352
396
|
</Tooltip>
|
|
353
|
-
<DropdownMenuContent
|
|
397
|
+
<DropdownMenuContent
|
|
398
|
+
align="end"
|
|
399
|
+
className={APP_ACTION_MENU_CONTENT_CLASS}
|
|
400
|
+
>
|
|
354
401
|
<DropdownMenuItem onSelect={onEdit}>
|
|
355
|
-
<IconEdit size={14}
|
|
402
|
+
<IconEdit size={14} aria-hidden="true" />
|
|
356
403
|
Edit details
|
|
357
404
|
</DropdownMenuItem>
|
|
358
405
|
{!isPending && !isArchived ? (
|
|
359
406
|
<>
|
|
360
407
|
<DropdownMenuItem onSelect={onKeys}>
|
|
361
|
-
<IconKey size={14}
|
|
408
|
+
<IconKey size={14} aria-hidden="true" />
|
|
362
409
|
Manage keys
|
|
363
410
|
</DropdownMenuItem>
|
|
364
411
|
<DropdownMenuItem onSelect={onResources}>
|
|
365
|
-
<IconFileText size={14}
|
|
412
|
+
<IconFileText size={14} aria-hidden="true" />
|
|
366
413
|
Agent resources
|
|
367
414
|
</DropdownMenuItem>
|
|
368
415
|
</>
|
|
@@ -372,28 +419,21 @@ function WorkspaceAppSettings({
|
|
|
372
419
|
onSelect={onRemovePending}
|
|
373
420
|
className="text-destructive focus:text-destructive"
|
|
374
421
|
>
|
|
375
|
-
<IconTrash size={14}
|
|
422
|
+
<IconTrash size={14} aria-hidden="true" />
|
|
376
423
|
Remove from list
|
|
377
424
|
</DropdownMenuItem>
|
|
378
425
|
) : isArchived ? (
|
|
379
426
|
<DropdownMenuItem onSelect={onUnarchive}>
|
|
380
|
-
<IconEye size={14}
|
|
427
|
+
<IconEye size={14} aria-hidden="true" />
|
|
381
428
|
Restore to list
|
|
382
429
|
</DropdownMenuItem>
|
|
383
430
|
) : (
|
|
384
431
|
<DropdownMenuItem onSelect={onArchive}>
|
|
385
|
-
<IconEyeOff size={14}
|
|
432
|
+
<IconEyeOff size={14} aria-hidden="true" />
|
|
386
433
|
Hide from list
|
|
387
434
|
</DropdownMenuItem>
|
|
388
435
|
)}
|
|
389
|
-
<
|
|
390
|
-
<DropdownMenuLabel className="font-normal">
|
|
391
|
-
<WorkspaceAppMetadata
|
|
392
|
-
app={app}
|
|
393
|
-
formatDate={formatDate}
|
|
394
|
-
labels={labels}
|
|
395
|
-
/>
|
|
396
|
-
</DropdownMenuLabel>
|
|
436
|
+
<WorkspaceAppMetadata app={app} labels={labels} />
|
|
397
437
|
</DropdownMenuContent>
|
|
398
438
|
</DropdownMenu>
|
|
399
439
|
);
|
|
@@ -401,51 +441,68 @@ function WorkspaceAppSettings({
|
|
|
401
441
|
|
|
402
442
|
function WorkspaceAppMetadata({
|
|
403
443
|
app,
|
|
404
|
-
formatDate,
|
|
405
444
|
labels,
|
|
406
445
|
}: {
|
|
407
446
|
app: WorkspaceAppSummary;
|
|
408
|
-
formatDate: ReturnType<typeof useFormatters>["formatDate"];
|
|
409
447
|
labels: WorkspaceAppMetadataLabels;
|
|
410
448
|
}) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
449
|
+
const trackedText = (value: string | null | undefined): string | null => {
|
|
450
|
+
const normalized = value?.trim();
|
|
451
|
+
return normalized && normalized.toLowerCase() !== "not tracked"
|
|
452
|
+
? normalized
|
|
453
|
+
: null;
|
|
454
|
+
};
|
|
455
|
+
const rows: Array<{
|
|
456
|
+
id: string;
|
|
457
|
+
icon: typeof IconUser;
|
|
458
|
+
label: string;
|
|
459
|
+
value: string;
|
|
460
|
+
}> = [];
|
|
461
|
+
|
|
462
|
+
const createdBy = trackedText(app.createdBy);
|
|
463
|
+
if (createdBy) {
|
|
464
|
+
rows.push({
|
|
465
|
+
id: "created-by",
|
|
466
|
+
icon: IconUser,
|
|
467
|
+
label: labels.createdBy,
|
|
468
|
+
value: createdBy,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
const owner = trackedText(app.owner);
|
|
472
|
+
if (owner) {
|
|
473
|
+
rows.push({
|
|
474
|
+
id: "owner",
|
|
475
|
+
icon: IconUser,
|
|
476
|
+
label: labels.owner,
|
|
477
|
+
value: owner,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
const teams = app.teams
|
|
481
|
+
?.map(trackedText)
|
|
482
|
+
.filter((team): team is string => Boolean(team))
|
|
483
|
+
.join(", ");
|
|
484
|
+
if (teams) {
|
|
485
|
+
rows.push({
|
|
486
|
+
id: "teams",
|
|
487
|
+
icon: IconUsersGroup,
|
|
488
|
+
label: labels.teams,
|
|
489
|
+
value: teams,
|
|
490
|
+
});
|
|
424
491
|
}
|
|
425
492
|
|
|
493
|
+
if (rows.length === 0) return null;
|
|
494
|
+
|
|
426
495
|
return (
|
|
427
|
-
|
|
428
|
-
<
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
/>
|
|
438
|
-
<WorkspaceAppMetadataRow
|
|
439
|
-
icon={IconUser}
|
|
440
|
-
label={labels.owner}
|
|
441
|
-
value={app.owner || labels.notTracked}
|
|
442
|
-
/>
|
|
443
|
-
<WorkspaceAppMetadataRow
|
|
444
|
-
icon={IconUsersGroup}
|
|
445
|
-
label={labels.teams}
|
|
446
|
-
value={app.teams?.join(", ") || labels.notTracked}
|
|
447
|
-
/>
|
|
448
|
-
</div>
|
|
496
|
+
<>
|
|
497
|
+
<DropdownMenuSeparator />
|
|
498
|
+
<DropdownMenuLabel className="font-normal">
|
|
499
|
+
<div className="flex flex-col gap-1.5 text-xs text-muted-foreground">
|
|
500
|
+
{rows.map((row) => (
|
|
501
|
+
<WorkspaceAppMetadataRow key={row.id} {...row} />
|
|
502
|
+
))}
|
|
503
|
+
</div>
|
|
504
|
+
</DropdownMenuLabel>
|
|
505
|
+
</>
|
|
449
506
|
);
|
|
450
507
|
}
|
|
451
508
|
|
|
@@ -454,7 +511,7 @@ function WorkspaceAppMetadataRow({
|
|
|
454
511
|
label,
|
|
455
512
|
value,
|
|
456
513
|
}: {
|
|
457
|
-
icon: typeof
|
|
514
|
+
icon: typeof IconUser;
|
|
458
515
|
label: string;
|
|
459
516
|
value: string;
|
|
460
517
|
}) {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import { IconSearch, IconX } from "@tabler/icons-react";
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
import { Button } from "./ui/button";
|
|
7
|
+
import { Input } from "./ui/input";
|
|
8
|
+
|
|
9
|
+
export function WorkspaceAppSearch({
|
|
10
|
+
query,
|
|
11
|
+
onQueryChange,
|
|
12
|
+
className,
|
|
13
|
+
}: {
|
|
14
|
+
query: string;
|
|
15
|
+
onQueryChange: (query: string) => void;
|
|
16
|
+
className?: string;
|
|
17
|
+
}) {
|
|
18
|
+
const t = useT();
|
|
19
|
+
const inputId = useId();
|
|
20
|
+
const label = t("dispatch.pages.searchApps", {
|
|
21
|
+
defaultValue: "Search apps",
|
|
22
|
+
});
|
|
23
|
+
const placeholder = t("dispatch.pages.searchAppsPlaceholder", {
|
|
24
|
+
defaultValue: "Search by name or description",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div role="search" className={cn("flex items-center gap-2", className)}>
|
|
29
|
+
<div className="relative min-w-0 flex-1">
|
|
30
|
+
<label htmlFor={inputId} className="sr-only">
|
|
31
|
+
{label}
|
|
32
|
+
</label>
|
|
33
|
+
<IconSearch
|
|
34
|
+
size={16}
|
|
35
|
+
aria-hidden="true"
|
|
36
|
+
className="pointer-events-none absolute start-3 top-1/2 -translate-y-1/2 text-muted-foreground"
|
|
37
|
+
/>
|
|
38
|
+
<Input
|
|
39
|
+
id={inputId}
|
|
40
|
+
value={query}
|
|
41
|
+
onChange={(event) => onQueryChange(event.target.value)}
|
|
42
|
+
placeholder={placeholder}
|
|
43
|
+
className="h-9 bg-background ps-9"
|
|
44
|
+
autoComplete="off"
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
{query ? (
|
|
48
|
+
<Button
|
|
49
|
+
type="button"
|
|
50
|
+
variant="ghost"
|
|
51
|
+
size="sm"
|
|
52
|
+
className="shrink-0 gap-1.5 text-muted-foreground"
|
|
53
|
+
onClick={() => onQueryChange("")}
|
|
54
|
+
>
|
|
55
|
+
<IconX size={14} aria-hidden="true" />
|
|
56
|
+
<span>
|
|
57
|
+
{t("dispatch.pages.clearAppSearch", {
|
|
58
|
+
defaultValue: "Clear",
|
|
59
|
+
})}
|
|
60
|
+
</span>
|
|
61
|
+
</Button>
|
|
62
|
+
) : null}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function WorkspaceAppSearchEmpty({
|
|
68
|
+
query,
|
|
69
|
+
onClear,
|
|
70
|
+
}: {
|
|
71
|
+
query: string;
|
|
72
|
+
onClear: () => void;
|
|
73
|
+
}) {
|
|
74
|
+
const t = useT();
|
|
75
|
+
return (
|
|
76
|
+
<div className="rounded-lg border border-dashed bg-card px-4 py-8 text-center">
|
|
77
|
+
<p className="text-sm font-medium text-foreground">
|
|
78
|
+
{t("dispatch.pages.noAppsMatch", {
|
|
79
|
+
defaultValue: "No apps match your search",
|
|
80
|
+
})}
|
|
81
|
+
</p>
|
|
82
|
+
<p className="mx-auto mt-1 max-w-md truncate text-xs text-muted-foreground">
|
|
83
|
+
“{query}”
|
|
84
|
+
</p>
|
|
85
|
+
<Button
|
|
86
|
+
type="button"
|
|
87
|
+
variant="outline"
|
|
88
|
+
size="sm"
|
|
89
|
+
className="mt-4"
|
|
90
|
+
onClick={onClear}
|
|
91
|
+
>
|
|
92
|
+
{t("dispatch.pages.clearAppSearch", { defaultValue: "Clear search" })}
|
|
93
|
+
</Button>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -3,6 +3,7 @@ import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
|
3
3
|
import { cn } from "../lib/utils";
|
|
4
4
|
import { ActionQueryError } from "./action-query-error";
|
|
5
5
|
import { Badge } from "./ui/badge";
|
|
6
|
+
import { Skeleton } from "./ui/skeleton";
|
|
6
7
|
|
|
7
8
|
export function appAvailabilityLabel(value?: string) {
|
|
8
9
|
switch (value) {
|
|
@@ -68,11 +69,11 @@ export function AppResourceEffectiveStack({
|
|
|
68
69
|
if (isLoading && !context) {
|
|
69
70
|
return (
|
|
70
71
|
<div className="mt-3 rounded-lg border bg-muted/20 p-3">
|
|
71
|
-
<
|
|
72
|
+
<Skeleton className="h-3 w-44" />
|
|
72
73
|
<div className="mt-3 grid gap-2 sm:grid-cols-3">
|
|
73
|
-
<
|
|
74
|
-
<
|
|
75
|
-
<
|
|
74
|
+
<Skeleton className="h-20 rounded-md" />
|
|
75
|
+
<Skeleton className="h-20 rounded-md" />
|
|
76
|
+
<Skeleton className="h-20 rounded-md" />
|
|
76
77
|
</div>
|
|
77
78
|
</div>
|
|
78
79
|
);
|
|
@@ -99,7 +100,11 @@ export function AppResourceEffectiveStack({
|
|
|
99
100
|
{resource.path}
|
|
100
101
|
</div>
|
|
101
102
|
</div>
|
|
102
|
-
|
|
103
|
+
{isLoading ? (
|
|
104
|
+
<Skeleton className="h-6 w-24 rounded-full" />
|
|
105
|
+
) : (
|
|
106
|
+
<Badge variant="outline">{appAvailabilityLabel(availability)}</Badge>
|
|
107
|
+
)}
|
|
103
108
|
</div>
|
|
104
109
|
|
|
105
110
|
<div className="mt-3 grid gap-2 sm:grid-cols-3">
|
|
@@ -3,6 +3,7 @@ import React, { act } from "react";
|
|
|
3
3
|
import { createRoot, type Root } from "react-dom/client";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
|
|
6
|
+
import { TooltipProvider } from "./ui/tooltip";
|
|
6
7
|
import {
|
|
7
8
|
WorkspaceTemplateCard,
|
|
8
9
|
WorkspaceTemplatesSection,
|
|
@@ -68,7 +69,11 @@ describe("WorkspaceTemplateCard", () => {
|
|
|
68
69
|
|
|
69
70
|
it("keeps template cards concise and moves setup context into the add flow", async () => {
|
|
70
71
|
await act(async () => {
|
|
71
|
-
root.render(
|
|
72
|
+
root.render(
|
|
73
|
+
<TooltipProvider>
|
|
74
|
+
<WorkspaceTemplateCard template={template} />
|
|
75
|
+
</TooltipProvider>,
|
|
76
|
+
);
|
|
72
77
|
});
|
|
73
78
|
|
|
74
79
|
expect(container.textContent).toContain("Weekly report");
|
|
@@ -94,9 +99,11 @@ describe("WorkspaceTemplateCard", () => {
|
|
|
94
99
|
|
|
95
100
|
await act(async () => {
|
|
96
101
|
root.render(
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
<TooltipProvider>
|
|
103
|
+
<WorkspaceTemplateCard
|
|
104
|
+
template={{ ...template, liveUrl: null, productUrl: null }}
|
|
105
|
+
/>
|
|
106
|
+
</TooltipProvider>,
|
|
100
107
|
);
|
|
101
108
|
});
|
|
102
109
|
expect(
|
|
@@ -107,7 +114,9 @@ describe("WorkspaceTemplateCard", () => {
|
|
|
107
114
|
it("creates an app with the default app id and allows an override", async () => {
|
|
108
115
|
await act(async () => {
|
|
109
116
|
root.render(
|
|
110
|
-
<
|
|
117
|
+
<TooltipProvider>
|
|
118
|
+
<WorkspaceTemplateCard template={template} defaultAppId="pipeline" />
|
|
119
|
+
</TooltipProvider>,
|
|
111
120
|
);
|
|
112
121
|
});
|
|
113
122
|
|
|
@@ -155,14 +164,32 @@ describe("WorkspaceTemplateCard", () => {
|
|
|
155
164
|
|
|
156
165
|
it("keeps add app available in catalog cards", async () => {
|
|
157
166
|
await act(async () => {
|
|
158
|
-
root.render(
|
|
167
|
+
root.render(
|
|
168
|
+
<TooltipProvider>
|
|
169
|
+
<WorkspaceTemplateCard template={template} catalog />
|
|
170
|
+
</TooltipProvider>,
|
|
171
|
+
);
|
|
159
172
|
});
|
|
160
173
|
|
|
161
|
-
const
|
|
162
|
-
|
|
174
|
+
const settingsButton = container.querySelector<HTMLButtonElement>(
|
|
175
|
+
'button[aria-label="Settings for Weekly report"]',
|
|
163
176
|
);
|
|
164
|
-
expect(
|
|
165
|
-
await act(async () =>
|
|
177
|
+
expect(settingsButton).not.toBeNull();
|
|
178
|
+
await act(async () => {
|
|
179
|
+
settingsButton?.dispatchEvent(
|
|
180
|
+
new MouseEvent("pointerdown", { bubbles: true, button: 0 }),
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
const addItem = Array.from(
|
|
184
|
+
document.querySelectorAll<HTMLElement>('[role="menuitem"]'),
|
|
185
|
+
).find((item) => item.textContent?.includes("Add app"));
|
|
186
|
+
expect(addItem).not.toBeUndefined();
|
|
187
|
+
const settingsMenu = document.querySelector<HTMLElement>('[role="menu"]');
|
|
188
|
+
expect(settingsMenu?.className).toContain("w-48");
|
|
189
|
+
expect(settingsMenu?.className).toContain("bg-popover");
|
|
190
|
+
expect(settingsMenu?.className).toContain("shadow-md");
|
|
191
|
+
expect(addItem?.querySelector("svg")).not.toBeNull();
|
|
192
|
+
await act(async () => addItem?.click());
|
|
166
193
|
expect(document.body.textContent).toContain("Choose the URL-safe id");
|
|
167
194
|
expect(
|
|
168
195
|
container.querySelector('a[href="https://reports.example.test"]'),
|
|
@@ -172,19 +199,21 @@ describe("WorkspaceTemplateCard", () => {
|
|
|
172
199
|
it("accepts the list action envelope and renders installed state", async () => {
|
|
173
200
|
await act(async () => {
|
|
174
201
|
root.render(
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
templates
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
202
|
+
<TooltipProvider>
|
|
203
|
+
<WorkspaceTemplatesSection
|
|
204
|
+
templates={{
|
|
205
|
+
templates: [
|
|
206
|
+
{
|
|
207
|
+
...template,
|
|
208
|
+
installed: true,
|
|
209
|
+
liveUrl: null,
|
|
210
|
+
productUrl: null,
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
}}
|
|
214
|
+
title="Curated templates"
|
|
215
|
+
/>
|
|
216
|
+
</TooltipProvider>,
|
|
188
217
|
);
|
|
189
218
|
});
|
|
190
219
|
|
|
@@ -13,6 +13,7 @@ import { cn } from "../lib/utils";
|
|
|
13
13
|
import { AppIcon } from "./app-icon";
|
|
14
14
|
import { AppListRow } from "./app-list-row";
|
|
15
15
|
import { AppOpenActions } from "./app-open-actions";
|
|
16
|
+
import { AppRowSettings } from "./app-row-settings";
|
|
16
17
|
import { Button } from "./ui/button";
|
|
17
18
|
import {
|
|
18
19
|
Dialog,
|
|
@@ -211,20 +212,16 @@ export function WorkspaceTemplateCard({
|
|
|
211
212
|
<AppOpenActions
|
|
212
213
|
name={template.name}
|
|
213
214
|
href={liveUrl}
|
|
214
|
-
target="_blank"
|
|
215
215
|
rel="noreferrer"
|
|
216
216
|
labels={{ openApp: labels.openApp }}
|
|
217
|
+
showNewTabOption
|
|
217
218
|
/>
|
|
218
219
|
) : null}
|
|
219
|
-
<
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
>
|
|
225
|
-
<IconPlus />
|
|
226
|
-
{labels.remix}
|
|
227
|
-
</Button>
|
|
220
|
+
<AppRowSettings
|
|
221
|
+
name={template.name}
|
|
222
|
+
addAppLabel={labels.remix}
|
|
223
|
+
onAddApp={() => setOpen(true)}
|
|
224
|
+
/>
|
|
228
225
|
</>
|
|
229
226
|
) : liveUrl ? (
|
|
230
227
|
<Button variant="ghost" size="sm" className="shrink-0" asChild>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
normalizeWorkspaceAppLayout,
|
|
5
|
+
orderWorkspaceApps,
|
|
6
|
+
toggleWorkspaceAppPinned,
|
|
7
|
+
workspaceAppMatchesQuery,
|
|
8
|
+
} from "./workspace-app-layout";
|
|
9
|
+
|
|
10
|
+
describe("workspace app layout", () => {
|
|
11
|
+
it("keeps pinned apps first without changing the remaining catalog order", () => {
|
|
12
|
+
const apps = [
|
|
13
|
+
{ id: "mail", name: "Mail" },
|
|
14
|
+
{ id: "analytics", name: "Analytics" },
|
|
15
|
+
{ id: "calendar", name: "Calendar" },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
expect(
|
|
19
|
+
orderWorkspaceApps(apps, {
|
|
20
|
+
pinnedIds: ["calendar", "calendar"],
|
|
21
|
+
orderedIds: ["mail", "analytics", "calendar"],
|
|
22
|
+
}).map((app) => app.id),
|
|
23
|
+
).toEqual(["calendar", "mail", "analytics"]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("matches app names and descriptions case-insensitively", () => {
|
|
27
|
+
expect(
|
|
28
|
+
workspaceAppMatchesQuery(
|
|
29
|
+
{ name: "Analytics", description: "Explore product health" },
|
|
30
|
+
"PRODUCT",
|
|
31
|
+
),
|
|
32
|
+
).toBe(true);
|
|
33
|
+
expect(workspaceAppMatchesQuery({ name: "Mail" }, "calendar")).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("normalizes persisted layout ids and toggles pin state", () => {
|
|
37
|
+
const layout = normalizeWorkspaceAppLayout({
|
|
38
|
+
pinnedIds: ["mail", "mail", 42],
|
|
39
|
+
orderedIds: ["calendar", "", "calendar"],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(toggleWorkspaceAppPinned(layout, "calendar")).toEqual({
|
|
43
|
+
pinnedIds: ["calendar", "mail"],
|
|
44
|
+
orderedIds: ["calendar"],
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|