@agent-native/dispatch 0.40.13 → 0.40.14-nightly-20261006010750
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/CHANGELOG.md +12 -0
- package/dist/components/connected-app-card.d.ts.map +1 -1
- package/dist/components/connected-app-card.js +23 -4
- package/dist/components/connected-app-card.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +116 -60
- 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 +35 -9
- package/dist/components/layout/workspace-apps-rail.js.map +1 -1
- package/dist/lib/other-apps.d.ts +1 -0
- package/dist/lib/other-apps.d.ts.map +1 -1
- package/dist/lib/other-apps.js +29 -1
- package/dist/lib/other-apps.js.map +1 -1
- package/dist/lib/workspace-apps.d.ts +2 -1
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +15 -5
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +17 -4
- package/dist/routes/pages/apps.js.map +1 -1
- package/package.json +4 -4
- package/src/components/connected-app-card.spec.tsx +100 -0
- package/src/components/connected-app-card.tsx +38 -9
- package/src/components/layout/Layout.spec.tsx +397 -6
- package/src/components/layout/Layout.tsx +173 -85
- package/src/components/layout/workspace-apps-rail.spec.tsx +69 -7
- package/src/components/layout/workspace-apps-rail.tsx +49 -7
- package/src/lib/other-apps.spec.ts +84 -2
- package/src/lib/other-apps.ts +39 -1
- package/src/lib/workspace-apps.spec.ts +53 -0
- package/src/lib/workspace-apps.ts +15 -7
- package/src/routes/pages/apps.spec.tsx +132 -0
- package/src/routes/pages/apps.tsx +46 -10
|
@@ -1,6 +1,88 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
|
-
import { filterOtherApps } from "./other-apps.js";
|
|
3
|
+
import { filterBuiltInApps, filterOtherApps } from "./other-apps.js";
|
|
4
|
+
|
|
5
|
+
describe("filterBuiltInApps", () => {
|
|
6
|
+
it("keeps configured first-party apps not mounted in the workspace", () => {
|
|
7
|
+
expect(
|
|
8
|
+
filterBuiltInApps(
|
|
9
|
+
[
|
|
10
|
+
{
|
|
11
|
+
id: "slides",
|
|
12
|
+
name: "Slides",
|
|
13
|
+
url: "https://slides.agent-native.com",
|
|
14
|
+
source: "builtin",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "clips",
|
|
18
|
+
name: "Clips",
|
|
19
|
+
url: "https://clips.agent-native.com",
|
|
20
|
+
source: "builtin",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "custom-agent",
|
|
24
|
+
name: "Custom agent",
|
|
25
|
+
url: "https://agent.example.test",
|
|
26
|
+
source: "custom",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "workspace-agent",
|
|
30
|
+
name: "Workspace agent",
|
|
31
|
+
url: "https://workspace.example.test",
|
|
32
|
+
source: "workspace",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "invalid",
|
|
36
|
+
name: "Invalid",
|
|
37
|
+
url: "/invalid",
|
|
38
|
+
source: "builtin",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
[{ id: "clips" }],
|
|
42
|
+
),
|
|
43
|
+
).toEqual([
|
|
44
|
+
{
|
|
45
|
+
id: "slides",
|
|
46
|
+
name: "Slides",
|
|
47
|
+
url: "https://slides.agent-native.com",
|
|
48
|
+
source: "builtin",
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("keeps built-in app launches on the current beta lane", () => {
|
|
54
|
+
vi.stubGlobal("window", {
|
|
55
|
+
location: { hostname: "beta.agent-workspace.builder.io" },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
expect(
|
|
60
|
+
filterBuiltInApps(
|
|
61
|
+
[
|
|
62
|
+
{
|
|
63
|
+
id: "clips",
|
|
64
|
+
name: "Clips",
|
|
65
|
+
url: "https://clips.agent-native.com/",
|
|
66
|
+
homeUrl: "https://clips.agent-native.com/recordings",
|
|
67
|
+
source: "builtin",
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
[],
|
|
71
|
+
),
|
|
72
|
+
).toEqual([
|
|
73
|
+
{
|
|
74
|
+
id: "clips",
|
|
75
|
+
name: "Clips",
|
|
76
|
+
url: "https://beta.clips.agent-native.com/",
|
|
77
|
+
homeUrl: "https://beta.clips.agent-native.com/recordings",
|
|
78
|
+
source: "builtin",
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
} finally {
|
|
82
|
+
vi.unstubAllGlobals();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
4
86
|
|
|
5
87
|
describe("filterOtherApps", () => {
|
|
6
88
|
it("keeps available linked apps while excluding workspace apps", () => {
|
package/src/lib/other-apps.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
defaultWorkspaceAppUrl,
|
|
3
|
+
isDefaultWorkspaceAppHiddenId,
|
|
4
|
+
} from "./workspace-apps";
|
|
2
5
|
|
|
3
6
|
export interface ConnectedAppSummary {
|
|
4
7
|
id: string;
|
|
@@ -54,3 +57,38 @@ export function filterOtherApps(
|
|
|
54
57
|
})
|
|
55
58
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
56
59
|
}
|
|
60
|
+
|
|
61
|
+
export function filterBuiltInApps(
|
|
62
|
+
connectedApps: ConnectedAppSummary[],
|
|
63
|
+
workspaceApps: WorkspaceAppId[],
|
|
64
|
+
): ConnectedAppSummary[] {
|
|
65
|
+
const workspaceAppIds = new Set([
|
|
66
|
+
"dispatch",
|
|
67
|
+
...workspaceApps.map((app) => app.id.trim().toLowerCase()),
|
|
68
|
+
]);
|
|
69
|
+
const seen = new Set<string>();
|
|
70
|
+
|
|
71
|
+
return connectedApps
|
|
72
|
+
.filter((app) => {
|
|
73
|
+
const id = app.id.trim().toLowerCase();
|
|
74
|
+
const url = app.homeUrl?.trim() || app.url;
|
|
75
|
+
if (
|
|
76
|
+
!id ||
|
|
77
|
+
app.source !== "builtin" ||
|
|
78
|
+
isDefaultWorkspaceAppHiddenId(id) ||
|
|
79
|
+
workspaceAppIds.has(id) ||
|
|
80
|
+
seen.has(id) ||
|
|
81
|
+
!isHttpUrl(url)
|
|
82
|
+
) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
seen.add(id);
|
|
86
|
+
return true;
|
|
87
|
+
})
|
|
88
|
+
.map((app) => ({
|
|
89
|
+
...app,
|
|
90
|
+
url: defaultWorkspaceAppUrl(app.url),
|
|
91
|
+
...(app.homeUrl ? { homeUrl: defaultWorkspaceAppUrl(app.homeUrl) } : {}),
|
|
92
|
+
}))
|
|
93
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
94
|
+
}
|
|
@@ -352,6 +352,25 @@ describe("workspace app routes", () => {
|
|
|
352
352
|
}
|
|
353
353
|
});
|
|
354
354
|
|
|
355
|
+
it("preserves configured built-in launch paths over canonical defaults", () => {
|
|
356
|
+
const apps = mergeChatFirstWorkspaceApps(
|
|
357
|
+
undefined,
|
|
358
|
+
["clips"],
|
|
359
|
+
[
|
|
360
|
+
{
|
|
361
|
+
id: "clips",
|
|
362
|
+
name: "Clips",
|
|
363
|
+
url: "https://clips.agent-native.com/recordings",
|
|
364
|
+
source: "builtin",
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
expect(apps.find((app) => app.id === "clips")?.url).toBe(
|
|
370
|
+
"https://clips.agent-native.com/recordings",
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
|
|
355
374
|
it("lets a mounted workspace app override a default row", () => {
|
|
356
375
|
const apps = mergeChatFirstWorkspaceApps(
|
|
357
376
|
[
|
|
@@ -385,6 +404,40 @@ describe("workspace app routes", () => {
|
|
|
385
404
|
]);
|
|
386
405
|
});
|
|
387
406
|
|
|
407
|
+
it("adds every configured first-party app to the launcher", () => {
|
|
408
|
+
const apps = mergeChatFirstWorkspaceApps(
|
|
409
|
+
[],
|
|
410
|
+
["mail", "clips", "slides"],
|
|
411
|
+
[
|
|
412
|
+
{
|
|
413
|
+
id: "mail",
|
|
414
|
+
name: "Mail",
|
|
415
|
+
url: "https://mail.agent-native.com",
|
|
416
|
+
source: "builtin",
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
id: "clips",
|
|
420
|
+
name: "Clips",
|
|
421
|
+
url: "https://clips.agent-native.com",
|
|
422
|
+
source: "builtin",
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
id: "slides",
|
|
426
|
+
name: "Slides",
|
|
427
|
+
url: "https://slides.agent-native.com",
|
|
428
|
+
source: "builtin",
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
expect(apps.map((app) => app.id)).toEqual(["mail", "clips", "slides"]);
|
|
434
|
+
expect(apps.map((app) => app.source)).toEqual([
|
|
435
|
+
"builtin",
|
|
436
|
+
"builtin",
|
|
437
|
+
"builtin",
|
|
438
|
+
]);
|
|
439
|
+
});
|
|
440
|
+
|
|
388
441
|
it("adds no defaults when built-ins are off or not yet resolved", () => {
|
|
389
442
|
const workspace = [{ id: "crm", name: "CRM", path: "/crm" }];
|
|
390
443
|
expect(
|
|
@@ -195,7 +195,7 @@ const DEFAULT_WORKSPACE_APP_DESCRIPTIONS: Record<
|
|
|
195
195
|
},
|
|
196
196
|
};
|
|
197
197
|
|
|
198
|
-
function defaultWorkspaceAppUrl(rawUrl: string): string {
|
|
198
|
+
export function defaultWorkspaceAppUrl(rawUrl: string): string {
|
|
199
199
|
if (typeof window === "undefined") return rawUrl;
|
|
200
200
|
|
|
201
201
|
const hostname = window.location.hostname
|
|
@@ -464,7 +464,7 @@ export function navigateToWorkspaceApp(href: string): boolean {
|
|
|
464
464
|
* of this workspace. `enabledBuiltinAppIds` must come from the server
|
|
465
465
|
* (list-connected-agents entries with source "builtin"), which already applies
|
|
466
466
|
* the builder's `agent-native.builtinAgents` config. While it is unknown, no
|
|
467
|
-
* default is added. `extraApps`
|
|
467
|
+
* default is added. `extraApps` fill missing rows and configured built-in URLs.
|
|
468
468
|
*/
|
|
469
469
|
export function mergeChatFirstWorkspaceApps(
|
|
470
470
|
apps: readonly WorkspaceAppSummary[] | undefined,
|
|
@@ -512,13 +512,21 @@ export function mergeChatFirstWorkspaceApps(
|
|
|
512
512
|
});
|
|
513
513
|
}
|
|
514
514
|
|
|
515
|
-
const existingIds = new Set(
|
|
516
|
-
[...merged.values()].map((app) => app.id.trim().toLowerCase()),
|
|
517
|
-
);
|
|
518
515
|
for (const app of extraApps) {
|
|
519
516
|
const id = app.id.trim().toLowerCase();
|
|
520
|
-
if (!id
|
|
521
|
-
|
|
517
|
+
if (!id) continue;
|
|
518
|
+
const existing = merged.get(id);
|
|
519
|
+
if (existing) {
|
|
520
|
+
if (existing.source === "builtin" && app.source === "builtin") {
|
|
521
|
+
merged.set(id, {
|
|
522
|
+
...existing,
|
|
523
|
+
name: app.name.trim() || existing.name,
|
|
524
|
+
description: app.description ?? existing.description,
|
|
525
|
+
url: app.url?.trim() || existing.url,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
522
530
|
merged.set(id, {
|
|
523
531
|
id,
|
|
524
532
|
name: app.name.trim() || id,
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import React, { act } from "react";
|
|
3
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
4
|
+
import { MemoryRouter } from "react-router";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
|
|
7
|
+
const queryState = vi.hoisted(() => ({
|
|
8
|
+
workspaceApps: [] as Array<Record<string, unknown>>,
|
|
9
|
+
workspaceAppsError: null as Error | null,
|
|
10
|
+
workspaceAppsLoading: false,
|
|
11
|
+
connectedApps: [] as Array<Record<string, unknown>>,
|
|
12
|
+
connectedAppsError: null as Error | null,
|
|
13
|
+
connectedAppsLoading: false,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock("@agent-native/core/client/hooks", () => ({
|
|
17
|
+
useActionQuery: (action: string) => {
|
|
18
|
+
const workspaceApps = action === "list-workspace-apps";
|
|
19
|
+
const connectedApps = action === "list-connected-agents";
|
|
20
|
+
const error = workspaceApps
|
|
21
|
+
? queryState.workspaceAppsError
|
|
22
|
+
: connectedApps
|
|
23
|
+
? queryState.connectedAppsError
|
|
24
|
+
: null;
|
|
25
|
+
return {
|
|
26
|
+
data: workspaceApps
|
|
27
|
+
? queryState.workspaceApps
|
|
28
|
+
: connectedApps
|
|
29
|
+
? queryState.connectedApps
|
|
30
|
+
: { name: null, displayName: null, appCount: 0 },
|
|
31
|
+
error,
|
|
32
|
+
isError: Boolean(error),
|
|
33
|
+
isLoading: workspaceApps
|
|
34
|
+
? queryState.workspaceAppsLoading
|
|
35
|
+
: connectedApps
|
|
36
|
+
? queryState.connectedAppsLoading
|
|
37
|
+
: false,
|
|
38
|
+
refetch: vi.fn(),
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
vi.mock("@agent-native/core/client/i18n", () => ({
|
|
44
|
+
useT: () => (key: string, values?: Record<string, unknown>) =>
|
|
45
|
+
key === "dispatch.pages.noWorkspaceApps"
|
|
46
|
+
? "No workspace apps"
|
|
47
|
+
: String(values?.defaultValue ?? key),
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
vi.mock("../../components/action-query-error", () => ({
|
|
51
|
+
ActionQueryError: ({ error }: { error: Error }) => (
|
|
52
|
+
<div role="alert">{error.message}</div>
|
|
53
|
+
),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
vi.mock("../../components/create-app-popover", () => ({
|
|
57
|
+
CreateAppPopover: () => null,
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
vi.mock("../../components/dispatch-shell", () => ({
|
|
61
|
+
DispatchShell: ({ children }: { children: React.ReactNode }) => (
|
|
62
|
+
<div>{children}</div>
|
|
63
|
+
),
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
vi.mock("../../components/workspace-app-search", () => ({
|
|
67
|
+
WorkspaceAppSearch: () => null,
|
|
68
|
+
WorkspaceAppSearchEmpty: () => <div>No search results</div>,
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
vi.mock("../../lib/workspace-app-layout", () => ({
|
|
72
|
+
orderWorkspaceApps: (apps: unknown[]) => apps,
|
|
73
|
+
useWorkspaceAppLayout: () => ({
|
|
74
|
+
layout: { pinnedIds: [], orderedIds: [] },
|
|
75
|
+
persistenceError: null,
|
|
76
|
+
togglePinned: vi.fn(),
|
|
77
|
+
}),
|
|
78
|
+
workspaceAppMatchesQuery: () => true,
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
import AppsRouteEntry from "./apps";
|
|
82
|
+
|
|
83
|
+
describe("Dispatch apps route", () => {
|
|
84
|
+
let container: HTMLDivElement;
|
|
85
|
+
let root: Root;
|
|
86
|
+
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
89
|
+
queryState.workspaceApps = [];
|
|
90
|
+
queryState.workspaceAppsError = null;
|
|
91
|
+
queryState.workspaceAppsLoading = false;
|
|
92
|
+
queryState.connectedApps = [];
|
|
93
|
+
queryState.connectedAppsError = null;
|
|
94
|
+
queryState.connectedAppsLoading = false;
|
|
95
|
+
container = document.createElement("div");
|
|
96
|
+
document.body.appendChild(container);
|
|
97
|
+
root = createRoot(container);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
afterEach(() => {
|
|
101
|
+
act(() => root.unmount());
|
|
102
|
+
container.remove();
|
|
103
|
+
vi.unstubAllGlobals();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
async function renderAppsRoute() {
|
|
107
|
+
await act(async () => {
|
|
108
|
+
root.render(
|
|
109
|
+
<MemoryRouter initialEntries={["/apps"]}>
|
|
110
|
+
<AppsRouteEntry />
|
|
111
|
+
</MemoryRouter>,
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
it("does not show the empty state when connected-app discovery fails", async () => {
|
|
117
|
+
queryState.connectedAppsError = new Error("Connected apps unavailable");
|
|
118
|
+
|
|
119
|
+
await renderAppsRoute();
|
|
120
|
+
|
|
121
|
+
expect(container.querySelector('[role="alert"]')?.textContent).toContain(
|
|
122
|
+
"Connected apps unavailable",
|
|
123
|
+
);
|
|
124
|
+
expect(container.textContent).not.toContain("No workspace apps");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("shows the empty state after successful app discovery", async () => {
|
|
128
|
+
await renderAppsRoute();
|
|
129
|
+
|
|
130
|
+
expect(container.textContent).toContain("No workspace apps");
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
APP_LIST_GRID_ROW_CLASS,
|
|
17
17
|
AppList,
|
|
18
18
|
} from "../../components/app-list-row";
|
|
19
|
+
import { ConnectedAppCard } from "../../components/connected-app-card";
|
|
19
20
|
import { CreateAppPopover } from "../../components/create-app-popover";
|
|
20
21
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
21
22
|
import { Button } from "../../components/ui/button";
|
|
@@ -30,6 +31,10 @@ import {
|
|
|
30
31
|
WorkspaceAppSearch,
|
|
31
32
|
WorkspaceAppSearchEmpty,
|
|
32
33
|
} from "../../components/workspace-app-search";
|
|
34
|
+
import {
|
|
35
|
+
filterBuiltInApps,
|
|
36
|
+
type ConnectedAppSummary,
|
|
37
|
+
} from "../../lib/other-apps";
|
|
33
38
|
import { cn } from "../../lib/utils";
|
|
34
39
|
import {
|
|
35
40
|
orderWorkspaceApps,
|
|
@@ -66,6 +71,10 @@ function AppsRoute() {
|
|
|
66
71
|
includeAgentCards: false,
|
|
67
72
|
includeArchived: true,
|
|
68
73
|
});
|
|
74
|
+
const connectedAppsQuery = useActionQuery<ConnectedAppSummary[]>(
|
|
75
|
+
"list-connected-agents",
|
|
76
|
+
{},
|
|
77
|
+
);
|
|
69
78
|
const { data: apps = [], isLoading: appsLoading } = appsQuery;
|
|
70
79
|
const { data: workspace } = useActionQuery(
|
|
71
80
|
"get-workspace-info",
|
|
@@ -81,6 +90,7 @@ function AppsRoute() {
|
|
|
81
90
|
const activeApps = visibleApps.filter((app) => app.status !== "pending");
|
|
82
91
|
const pendingApps = visibleApps.filter((app) => app.status === "pending");
|
|
83
92
|
const archivedApps = allApps.filter((app) => app.archived);
|
|
93
|
+
const defaultApps = filterBuiltInApps(connectedAppsQuery.data ?? [], allApps);
|
|
84
94
|
const orderedActiveApps = orderWorkspaceApps(activeApps, layout);
|
|
85
95
|
const orderedPendingApps = orderWorkspaceApps(pendingApps, layout);
|
|
86
96
|
const orderedArchivedApps = orderWorkspaceApps(archivedApps, layout);
|
|
@@ -93,11 +103,19 @@ function AppsRoute() {
|
|
|
93
103
|
const filteredArchivedApps = orderedArchivedApps.filter((app) =>
|
|
94
104
|
workspaceAppMatchesQuery(app, searchQuery),
|
|
95
105
|
);
|
|
106
|
+
const filteredDefaultApps = defaultApps.filter((app) =>
|
|
107
|
+
workspaceAppMatchesQuery(app, searchQuery),
|
|
108
|
+
);
|
|
96
109
|
const hasSearchResults =
|
|
97
110
|
filteredActiveApps.length > 0 ||
|
|
98
111
|
filteredPendingApps.length > 0 ||
|
|
99
|
-
filteredArchivedApps.length > 0
|
|
100
|
-
|
|
112
|
+
filteredArchivedApps.length > 0 ||
|
|
113
|
+
filteredDefaultApps.length > 0;
|
|
114
|
+
const showAppSkeletons =
|
|
115
|
+
(appsLoading || connectedAppsQuery.isLoading) &&
|
|
116
|
+
allApps.length === 0 &&
|
|
117
|
+
defaultApps.length === 0;
|
|
118
|
+
const appDiscoveryFailed = appsQuery.isError || connectedAppsQuery.isError;
|
|
101
119
|
|
|
102
120
|
return (
|
|
103
121
|
<DispatchShell
|
|
@@ -132,7 +150,9 @@ function AppsRoute() {
|
|
|
132
150
|
onQueryChange={setSearchQuery}
|
|
133
151
|
/>
|
|
134
152
|
) : null}
|
|
135
|
-
{activeApps.length > 0 ||
|
|
153
|
+
{activeApps.length > 0 ||
|
|
154
|
+
pendingApps.length > 0 ||
|
|
155
|
+
defaultApps.length > 0 ? (
|
|
136
156
|
<CreateAppPopover
|
|
137
157
|
align="end"
|
|
138
158
|
trigger={
|
|
@@ -165,14 +185,17 @@ function AppsRoute() {
|
|
|
165
185
|
error={appsQuery.error}
|
|
166
186
|
onRetry={() => void appsQuery.refetch()}
|
|
167
187
|
/>
|
|
168
|
-
) :
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
onClear={() => setSearchQuery("")}
|
|
188
|
+
) : null}
|
|
189
|
+
{connectedAppsQuery.isError ? (
|
|
190
|
+
<ActionQueryError
|
|
191
|
+
error={connectedAppsQuery.error}
|
|
192
|
+
onRetry={() => void connectedAppsQuery.refetch()}
|
|
174
193
|
/>
|
|
175
|
-
) :
|
|
194
|
+
) : null}
|
|
195
|
+
{showAppSkeletons ? (
|
|
196
|
+
<AppsSkeletonGrid />
|
|
197
|
+
) : filteredActiveApps.length > 0 ||
|
|
198
|
+
filteredDefaultApps.length > 0 ? (
|
|
176
199
|
<AppList className={APP_LIST_GRID_CLASS}>
|
|
177
200
|
{filteredActiveApps.map((app) => (
|
|
178
201
|
<WorkspaceAppCard
|
|
@@ -183,7 +206,20 @@ function AppsRoute() {
|
|
|
183
206
|
onTogglePinned={() => togglePinned(app.id)}
|
|
184
207
|
/>
|
|
185
208
|
))}
|
|
209
|
+
{filteredDefaultApps.map((app) => (
|
|
210
|
+
<ConnectedAppCard
|
|
211
|
+
key={app.id}
|
|
212
|
+
app={app}
|
|
213
|
+
className={APP_LIST_GRID_ROW_CLASS}
|
|
214
|
+
/>
|
|
215
|
+
))}
|
|
186
216
|
</AppList>
|
|
217
|
+
) : appDiscoveryFailed ? null : !hasSearchResults &&
|
|
218
|
+
searchQuery.trim() ? (
|
|
219
|
+
<WorkspaceAppSearchEmpty
|
|
220
|
+
query={searchQuery}
|
|
221
|
+
onClear={() => setSearchQuery("")}
|
|
222
|
+
/>
|
|
187
223
|
) : searchQuery.trim() ? null : pendingApps.length > 0 ? (
|
|
188
224
|
<EmptyActiveAppsState />
|
|
189
225
|
) : (
|