@agent-native/dispatch 0.23.2 → 0.23.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/components/app-open-actions.d.ts.map +1 -1
- package/dist/components/app-open-actions.js +3 -2
- package/dist/components/app-open-actions.js.map +1 -1
- package/dist/components/layout/Layout.d.ts +4 -2
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +83 -12
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/layout/workspace-apps-rail.d.ts.map +1 -1
- package/dist/components/layout/workspace-apps-rail.js +2 -22
- package/dist/components/layout/workspace-apps-rail.js.map +1 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +3 -4
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-template-card.d.ts.map +1 -1
- package/dist/components/workspace-template-card.js +1 -1
- package/dist/components/workspace-template-card.js.map +1 -1
- package/dist/lib/workspace-apps.d.ts +1 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +13 -0
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/routes/index.js +1 -1
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/pages/agent-integrations.d.ts +5 -0
- package/dist/routes/pages/agent-integrations.d.ts.map +1 -0
- package/dist/routes/pages/agent-integrations.js +15 -0
- package/dist/routes/pages/agent-integrations.js.map +1 -0
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +14 -2
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/overview.d.ts +2 -2
- package/dist/routes/pages/settings.d.ts.map +1 -1
- package/dist/routes/pages/settings.js +1 -1
- package/dist/routes/pages/settings.js.map +1 -1
- package/package.json +2 -2
- package/src/components/app-open-actions.tsx +3 -2
- package/src/components/layout/Layout.tsx +111 -16
- package/src/components/layout/workspace-apps-rail.tsx +2 -25
- package/src/components/workspace-app-card.spec.tsx +6 -6
- package/src/components/workspace-app-card.tsx +4 -7
- package/src/components/workspace-template-card.spec.tsx +4 -16
- package/src/components/workspace-template-card.tsx +0 -1
- package/src/lib/workspace-apps.spec.ts +21 -0
- package/src/lib/workspace-apps.ts +12 -0
- package/src/routes/index.ts +1 -1
- package/src/routes/pages/agent-integrations.tsx +24 -0
- package/src/routes/pages/apps.$appId.tsx +19 -1
- package/src/routes/pages/settings.tsx +1 -19
|
@@ -153,7 +153,7 @@ describe("WorkspaceTemplateCard", () => {
|
|
|
153
153
|
);
|
|
154
154
|
});
|
|
155
155
|
|
|
156
|
-
it("keeps add app
|
|
156
|
+
it("keeps add app available in catalog cards", async () => {
|
|
157
157
|
await act(async () => {
|
|
158
158
|
root.render(<WorkspaceTemplateCard template={template} catalog />);
|
|
159
159
|
});
|
|
@@ -162,23 +162,11 @@ describe("WorkspaceTemplateCard", () => {
|
|
|
162
162
|
(button) => button.textContent?.includes("Add app"),
|
|
163
163
|
);
|
|
164
164
|
expect(addButton).toBeDefined();
|
|
165
|
-
|
|
166
|
-
const optionsButton = container.querySelector<HTMLButtonElement>(
|
|
167
|
-
'button[aria-label="Open options for Weekly report"]',
|
|
168
|
-
);
|
|
169
|
-
expect(optionsButton).not.toBeNull();
|
|
170
|
-
await act(async () => {
|
|
171
|
-
optionsButton?.dispatchEvent(
|
|
172
|
-
new MouseEvent("pointerdown", { bubbles: true, button: 0 }),
|
|
173
|
-
);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
const addItem = Array.from(
|
|
177
|
-
document.querySelectorAll<HTMLElement>('[role="menuitem"]'),
|
|
178
|
-
).find((item) => item.textContent?.includes("Add app"));
|
|
179
|
-
expect(addItem).toBeUndefined();
|
|
180
165
|
await act(async () => addButton?.click());
|
|
181
166
|
expect(document.body.textContent).toContain("Choose the URL-safe id");
|
|
167
|
+
expect(
|
|
168
|
+
container.querySelector('a[href="https://reports.example.test"]'),
|
|
169
|
+
).not.toBeNull();
|
|
182
170
|
});
|
|
183
171
|
|
|
184
172
|
it("accepts the list action envelope and renders installed state", async () => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { workspaceAppIdFromRoute, workspaceAppRoute } from "./workspace-apps";
|
|
4
|
+
|
|
5
|
+
describe("workspace app routes", () => {
|
|
6
|
+
it("round-trips encoded app ids", () => {
|
|
7
|
+
const route = workspaceAppRoute("sales ops");
|
|
8
|
+
expect(route).toBe("/apps/sales%20ops");
|
|
9
|
+
expect(workspaceAppIdFromRoute(route)).toBe("sales ops");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("does not mark app paths or the apps index as an active app route", () => {
|
|
13
|
+
expect(workspaceAppIdFromRoute("/sales-ops")).toBeNull();
|
|
14
|
+
expect(workspaceAppIdFromRoute("/apps")).toBeNull();
|
|
15
|
+
expect(workspaceAppIdFromRoute("/overview")).toBeNull();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("accepts nested app routes while preserving the app id", () => {
|
|
19
|
+
expect(workspaceAppIdFromRoute("/apps/mail/inbox")).toBe("mail");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -31,6 +31,18 @@ export function workspaceAppRoute(appId: string): string {
|
|
|
31
31
|
return `/apps/${encodeURIComponent(appId)}`;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export function workspaceAppIdFromRoute(pathname: string): string | null {
|
|
35
|
+
const match = pathname.match(/^\/apps\/([^/]+)(?:\/|$)/);
|
|
36
|
+
if (!match) return null;
|
|
37
|
+
try {
|
|
38
|
+
const appId = decodeURIComponent(match[1]).trim();
|
|
39
|
+
return appId || null;
|
|
40
|
+
} catch {
|
|
41
|
+
// coercion-ok: malformed app routes are inactive, not app ids.
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
export function workspaceAppHref(app: WorkspaceAppSummary): string | null {
|
|
35
47
|
if (app.status === "pending") {
|
|
36
48
|
return app.builderUrl
|
package/src/routes/index.ts
CHANGED
|
@@ -44,7 +44,7 @@ export const dispatchRoutes: RouteConfig = [
|
|
|
44
44
|
route("automations", "./pages/automations.js"),
|
|
45
45
|
route("approvals", "./pages/approvals.js"),
|
|
46
46
|
route("destinations", "./pages/destinations.js"),
|
|
47
|
-
route("integrations", "./pages/integrations.js"),
|
|
47
|
+
route("integrations", "./pages/agent-integrations.js"),
|
|
48
48
|
route("transactional-email", "./pages/transactional-email.js"),
|
|
49
49
|
route(
|
|
50
50
|
"transactional-email/:appId/:id",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useAgentSettingsTabs } from "@agent-native/core/client/settings";
|
|
2
|
+
|
|
3
|
+
import { DispatchShell } from "../../components/dispatch-shell";
|
|
4
|
+
|
|
5
|
+
export function meta() {
|
|
6
|
+
return [{ title: "Integrations — Dispatch" }];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function AgentIntegrationsRoute() {
|
|
10
|
+
const settingsTabs = useAgentSettingsTabs();
|
|
11
|
+
const integrationsTab = settingsTabs.find((tab) => tab.id === "integrations");
|
|
12
|
+
if (!integrationsTab) {
|
|
13
|
+
throw new Error("The shared Integrations settings view is unavailable.");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<DispatchShell
|
|
18
|
+
title="Integrations"
|
|
19
|
+
description="Connect the tools and services available to your agent."
|
|
20
|
+
>
|
|
21
|
+
<div className="mx-auto w-full max-w-5xl">{integrationsTab.content}</div>
|
|
22
|
+
</DispatchShell>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import { useT } from "@agent-native/core/client/i18n";
|
|
6
6
|
import { withBuilderUtmTrackingParams } from "@agent-native/core/shared/builder-link-tracking";
|
|
7
7
|
import { IconArrowLeft, IconClockHour4 } from "@tabler/icons-react";
|
|
8
|
-
import { useEffect, useMemo, useState } from "react";
|
|
8
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
9
9
|
import { Link, useParams } from "react-router";
|
|
10
10
|
|
|
11
11
|
import { ActionQueryError } from "../../components/action-query-error";
|
|
@@ -52,6 +52,7 @@ export default function WorkspaceAppRoute() {
|
|
|
52
52
|
const [embedUrl, setEmbedUrl] = useState<string | null>(null);
|
|
53
53
|
const [embedError, setEmbedError] = useState<Error | null>(null);
|
|
54
54
|
const [embedAttempt, setEmbedAttempt] = useState(0);
|
|
55
|
+
const embedFrameRef = useRef<HTMLIFrameElement>(null);
|
|
55
56
|
const createEmbedSession = useActionMutation<
|
|
56
57
|
EmbedSessionResult,
|
|
57
58
|
EmbedSessionInput
|
|
@@ -96,6 +97,22 @@ export default function WorkspaceAppRoute() {
|
|
|
96
97
|
embedInput,
|
|
97
98
|
]);
|
|
98
99
|
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const handleEmbedSessionExpired = (event: MessageEvent) => {
|
|
102
|
+
if (
|
|
103
|
+
event.data?.type !== "agentNative.embedSessionExpired" ||
|
|
104
|
+
event.source !== embedFrameRef.current?.contentWindow
|
|
105
|
+
) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
setEmbedAttempt((attempt) => attempt + 1);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
window.addEventListener("message", handleEmbedSessionExpired);
|
|
112
|
+
return () =>
|
|
113
|
+
window.removeEventListener("message", handleEmbedSessionExpired);
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
99
116
|
if (appsQuery.isError) {
|
|
100
117
|
return (
|
|
101
118
|
<div className="flex h-full min-h-0 items-center justify-center p-6">
|
|
@@ -208,6 +225,7 @@ export default function WorkspaceAppRoute() {
|
|
|
208
225
|
data-dispatch-workspace-app-frame
|
|
209
226
|
src={embedUrl}
|
|
210
227
|
title={app.name}
|
|
228
|
+
ref={embedFrameRef}
|
|
211
229
|
referrerPolicy="no-referrer"
|
|
212
230
|
className="h-full w-full border-0 bg-background"
|
|
213
231
|
/>
|
|
@@ -68,7 +68,7 @@ export default function SettingsRoute() {
|
|
|
68
68
|
description={t("settings.description")}
|
|
69
69
|
>
|
|
70
70
|
<SettingsTabsPage
|
|
71
|
-
extraTabs={agentSettingsTabs}
|
|
71
|
+
extraTabs={agentSettingsTabs.filter((tab) => tab.id !== "integrations")}
|
|
72
72
|
general={
|
|
73
73
|
<div className="mx-auto w-full max-w-3xl space-y-6">
|
|
74
74
|
<Card>
|
|
@@ -141,24 +141,6 @@ export default function SettingsRoute() {
|
|
|
141
141
|
</CardContent>
|
|
142
142
|
</Card>
|
|
143
143
|
|
|
144
|
-
<Card>
|
|
145
|
-
<CardHeader>
|
|
146
|
-
<CardTitle className="text-base">
|
|
147
|
-
{t("settings.automationsTitle")}
|
|
148
|
-
</CardTitle>
|
|
149
|
-
<CardDescription>
|
|
150
|
-
{t("settings.automationsDescription")}
|
|
151
|
-
</CardDescription>
|
|
152
|
-
</CardHeader>
|
|
153
|
-
<CardContent>
|
|
154
|
-
<Button variant="outline" asChild>
|
|
155
|
-
<Link to="/admin/automations">
|
|
156
|
-
{t("settings.openAutomations")}
|
|
157
|
-
</Link>
|
|
158
|
-
</Button>
|
|
159
|
-
</CardContent>
|
|
160
|
-
</Card>
|
|
161
|
-
|
|
162
144
|
<Card>
|
|
163
145
|
<CardHeader>
|
|
164
146
|
<CardTitle className="text-base">
|