@shipfox/client-onboarding 10.0.1 → 12.0.0
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +28 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/dist/workspace-setup-route.d.ts.map +1 -1
- package/dist/workspace-setup-route.js +27 -1
- package/dist/workspace-setup-route.js.map +1 -1
- package/package.json +5 -5
- package/src/workspace-setup-route.test.tsx +69 -1
- package/src/workspace-setup-route.ts +31 -1
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-setup-route.d.ts","sourceRoot":"","sources":["../src/workspace-setup-route.ts"],"names":[],"mappings":"AASA,OAAO,
|
|
1
|
+
{"version":3,"file":"workspace-setup-route.d.ts","sourceRoot":"","sources":["../src/workspace-setup-route.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AAKvD,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,uBAAuB,CAAC,EAC5C,WAAW,EACX,WAAW,EACX,QAAQ,GACT,EAAE,0BAA0B,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAsE3D"}
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import { isModelProviderOnboardingDismissed, modelProviderConfigsQueryOptions } from '@shipfox/client-agent';
|
|
2
2
|
import { sourceConnectionsQueryOptions } from '@shipfox/client-integrations';
|
|
3
3
|
import { projectExistenceQueryOptions } from '@shipfox/client-projects';
|
|
4
|
-
import { WorkspaceSetupLoadError } from '@shipfox/client-shell/runtime';
|
|
4
|
+
import { userWorkspacesQueryOptions, WorkspaceSetupLoadError } from '@shipfox/client-shell/runtime';
|
|
5
5
|
import { redirect } from '@tanstack/react-router';
|
|
6
6
|
const TRAILING_SLASHES_RE = /\/+$/u;
|
|
7
7
|
export async function loadWorkspaceSetupRoute({ queryClient, workspaceId, pathname }) {
|
|
8
|
+
const workspace = await fetchWorkspaceSummary(queryClient, workspaceId);
|
|
9
|
+
const workspaceStatus = workspace?.status;
|
|
10
|
+
switch(workspaceStatus){
|
|
11
|
+
case undefined:
|
|
12
|
+
case 'active':
|
|
13
|
+
break;
|
|
14
|
+
case 'suspended':
|
|
15
|
+
case 'deleted':
|
|
16
|
+
return {
|
|
17
|
+
hideProjectNavigation: true,
|
|
18
|
+
unavailable: true
|
|
19
|
+
};
|
|
20
|
+
default:
|
|
21
|
+
return assertNever(workspaceStatus);
|
|
22
|
+
}
|
|
8
23
|
const projects = await fetchWorkspaceProjectExistence(queryClient, workspaceId);
|
|
9
24
|
const normalizedPathname = normalizePath(pathname);
|
|
10
25
|
if (projects.projects.length > 0) {
|
|
@@ -70,6 +85,17 @@ export async function loadWorkspaceSetupRoute({ queryClient, workspaceId, pathna
|
|
|
70
85
|
replace: true
|
|
71
86
|
});
|
|
72
87
|
}
|
|
88
|
+
function assertNever(value) {
|
|
89
|
+
throw new Error(`Unhandled workspace status: ${JSON.stringify(value)}`);
|
|
90
|
+
}
|
|
91
|
+
async function fetchWorkspaceSummary(queryClient, workspaceId) {
|
|
92
|
+
try {
|
|
93
|
+
const result = await queryClient.fetchQuery(userWorkspacesQueryOptions());
|
|
94
|
+
return result.memberships.find((workspace)=>workspace.id === workspaceId);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw new WorkspaceSetupLoadError(error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
73
99
|
async function fetchWorkspaceProjectExistence(queryClient, workspaceId) {
|
|
74
100
|
const options = projectExistenceQueryOptions(workspaceId);
|
|
75
101
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/workspace-setup-route.ts"],"sourcesContent":["import {\n isModelProviderOnboardingDismissed,\n modelProviderConfigsQueryOptions,\n} from '@shipfox/client-agent';\nimport {\n type IntegrationConnection,\n sourceConnectionsQueryOptions,\n} from '@shipfox/client-integrations';\nimport {projectExistenceQueryOptions} from '@shipfox/client-projects';\nimport {WorkspaceSetupLoadError, type WorkspaceSetupState} from '@shipfox/client-shell/runtime';\nimport type {QueryClient} from '@tanstack/react-query';\nimport {redirect} from '@tanstack/react-router';\n\nconst TRAILING_SLASHES_RE = /\\/+$/u;\n\nexport interface WorkspaceSetupRouteOptions {\n queryClient: QueryClient;\n workspaceId: string;\n pathname: string;\n}\n\nexport async function loadWorkspaceSetupRoute({\n queryClient,\n workspaceId,\n pathname,\n}: WorkspaceSetupRouteOptions): Promise<WorkspaceSetupState> {\n const projects = await fetchWorkspaceProjectExistence(queryClient, workspaceId);\n const normalizedPathname = normalizePath(pathname);\n\n if (projects.projects.length > 0) {\n if (isIntegrationsIndexPath(normalizedPathname, workspaceId)) {\n throw redirect({\n to: '/workspaces/$wid/settings/integrations',\n params: {wid: workspaceId},\n replace: true,\n });\n }\n\n return {hideProjectNavigation: false};\n }\n\n const sourceConnections = await fetchWorkspaceSourceConnections(queryClient, workspaceId);\n const hasSourceConnection = sourceConnections.length > 0;\n\n if (!hasSourceConnection) {\n if (isIntegrationSetupPath(normalizedPathname, workspaceId)) {\n return {hideProjectNavigation: true};\n }\n\n throw redirect({\n to: '/workspaces/$wid/integrations',\n params: {wid: workspaceId},\n replace: true,\n });\n }\n\n if (isAgentSettingsPath(normalizedPathname, workspaceId)) {\n return {hideProjectNavigation: true};\n }\n\n const providerHandled = await hasHandledModelProviderOnboarding(queryClient, workspaceId);\n if (!providerHandled) {\n if (isModelProviderOnboardingPath(normalizedPathname, workspaceId)) {\n return {hideProjectNavigation: true};\n }\n\n throw redirect({\n to: '/workspaces/$wid/model-provider',\n params: {wid: workspaceId},\n replace: true,\n });\n }\n\n if (isProjectCreationPath(normalizedPathname, workspaceId)) {\n return {hideProjectNavigation: true};\n }\n\n throw redirect({\n to: '/workspaces/$wid/projects/new',\n params: {wid: workspaceId},\n replace: true,\n });\n}\n\nasync function fetchWorkspaceProjectExistence(queryClient: QueryClient, workspaceId: string) {\n const options = projectExistenceQueryOptions(workspaceId);\n\n try {\n return await queryClient.fetchQuery(options);\n } catch (error) {\n const cached = queryClient.getQueryData<{projects: unknown[]}>(options.queryKey);\n if (cached !== undefined) return cached;\n throw new WorkspaceSetupLoadError(error);\n }\n}\n\nasync function fetchWorkspaceSourceConnections(queryClient: QueryClient, workspaceId: string) {\n const options = sourceConnectionsQueryOptions(workspaceId);\n\n try {\n return await queryClient.fetchQuery(options);\n } catch (error) {\n const cached = queryClient.getQueryData<IntegrationConnection[]>(options.queryKey);\n if (cached !== undefined) return cached;\n throw new WorkspaceSetupLoadError(error);\n }\n}\n\nasync function hasHandledModelProviderOnboarding(\n queryClient: QueryClient,\n workspaceId: string,\n): Promise<boolean> {\n if (isModelProviderOnboardingDismissed(workspaceId)) return true;\n\n const options = modelProviderConfigsQueryOptions(workspaceId);\n try {\n const configs = await queryClient.fetchQuery(options);\n return configs.configs.length > 0;\n } catch {\n const cached = queryClient.getQueryData<{configs: unknown[]}>(options.queryKey);\n return cached?.configs.length !== 0;\n }\n}\n\nfunction normalizePath(pathname: string) {\n if (pathname === '/') return pathname;\n return pathname.replace(TRAILING_SLASHES_RE, '');\n}\n\nfunction workspacePath(workspaceId: string, suffix: string) {\n return `/workspaces/${workspaceId}${suffix}`;\n}\n\nfunction isIntegrationsIndexPath(pathname: string, workspaceId: string) {\n return pathname === workspacePath(workspaceId, '/integrations');\n}\n\nfunction isIntegrationSetupPath(pathname: string, workspaceId: string) {\n const basePath = workspacePath(workspaceId, '/integrations');\n return (\n pathname === basePath ||\n pathname.startsWith(`${basePath}/`) ||\n pathname === workspacePath(workspaceId, '/settings/integrations')\n );\n}\n\nfunction isProjectCreationPath(pathname: string, workspaceId: string) {\n return pathname === workspacePath(workspaceId, '/projects/new');\n}\n\nfunction isModelProviderOnboardingPath(pathname: string, workspaceId: string) {\n return pathname === workspacePath(workspaceId, '/model-provider');\n}\n\nfunction isAgentSettingsPath(pathname: string, workspaceId: string) {\n return pathname === workspacePath(workspaceId, '/settings/agents');\n}\n"],"names":["isModelProviderOnboardingDismissed","modelProviderConfigsQueryOptions","sourceConnectionsQueryOptions","projectExistenceQueryOptions","WorkspaceSetupLoadError","redirect","TRAILING_SLASHES_RE","loadWorkspaceSetupRoute","queryClient","workspaceId","pathname","projects","fetchWorkspaceProjectExistence","normalizedPathname","normalizePath","length","isIntegrationsIndexPath","to","params","wid","replace","hideProjectNavigation","sourceConnections","fetchWorkspaceSourceConnections","hasSourceConnection","isIntegrationSetupPath","isAgentSettingsPath","providerHandled","hasHandledModelProviderOnboarding","isModelProviderOnboardingPath","isProjectCreationPath","options","fetchQuery","error","cached","getQueryData","queryKey","undefined","configs","workspacePath","suffix","basePath","startsWith"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/workspace-setup-route.ts"],"sourcesContent":["import {\n isModelProviderOnboardingDismissed,\n modelProviderConfigsQueryOptions,\n} from '@shipfox/client-agent';\nimport {\n type IntegrationConnection,\n sourceConnectionsQueryOptions,\n} from '@shipfox/client-integrations';\nimport {projectExistenceQueryOptions} from '@shipfox/client-projects';\nimport {\n userWorkspacesQueryOptions,\n WorkspaceSetupLoadError,\n type WorkspaceSetupState,\n} from '@shipfox/client-shell/runtime';\nimport type {QueryClient} from '@tanstack/react-query';\nimport {redirect} from '@tanstack/react-router';\n\nconst TRAILING_SLASHES_RE = /\\/+$/u;\n\nexport interface WorkspaceSetupRouteOptions {\n queryClient: QueryClient;\n workspaceId: string;\n pathname: string;\n}\n\nexport async function loadWorkspaceSetupRoute({\n queryClient,\n workspaceId,\n pathname,\n}: WorkspaceSetupRouteOptions): Promise<WorkspaceSetupState> {\n const workspace = await fetchWorkspaceSummary(queryClient, workspaceId);\n const workspaceStatus = workspace?.status;\n switch (workspaceStatus) {\n case undefined:\n case 'active':\n break;\n case 'suspended':\n case 'deleted':\n return {hideProjectNavigation: true, unavailable: true};\n default:\n return assertNever(workspaceStatus);\n }\n\n const projects = await fetchWorkspaceProjectExistence(queryClient, workspaceId);\n const normalizedPathname = normalizePath(pathname);\n\n if (projects.projects.length > 0) {\n if (isIntegrationsIndexPath(normalizedPathname, workspaceId)) {\n throw redirect({\n to: '/workspaces/$wid/settings/integrations',\n params: {wid: workspaceId},\n replace: true,\n });\n }\n\n return {hideProjectNavigation: false};\n }\n\n const sourceConnections = await fetchWorkspaceSourceConnections(queryClient, workspaceId);\n const hasSourceConnection = sourceConnections.length > 0;\n\n if (!hasSourceConnection) {\n if (isIntegrationSetupPath(normalizedPathname, workspaceId)) {\n return {hideProjectNavigation: true};\n }\n\n throw redirect({\n to: '/workspaces/$wid/integrations',\n params: {wid: workspaceId},\n replace: true,\n });\n }\n\n if (isAgentSettingsPath(normalizedPathname, workspaceId)) {\n return {hideProjectNavigation: true};\n }\n\n const providerHandled = await hasHandledModelProviderOnboarding(queryClient, workspaceId);\n if (!providerHandled) {\n if (isModelProviderOnboardingPath(normalizedPathname, workspaceId)) {\n return {hideProjectNavigation: true};\n }\n\n throw redirect({\n to: '/workspaces/$wid/model-provider',\n params: {wid: workspaceId},\n replace: true,\n });\n }\n\n if (isProjectCreationPath(normalizedPathname, workspaceId)) {\n return {hideProjectNavigation: true};\n }\n\n throw redirect({\n to: '/workspaces/$wid/projects/new',\n params: {wid: workspaceId},\n replace: true,\n });\n}\n\nfunction assertNever(value: never): never {\n throw new Error(`Unhandled workspace status: ${JSON.stringify(value)}`);\n}\n\nasync function fetchWorkspaceSummary(queryClient: QueryClient, workspaceId: string) {\n try {\n const result = await queryClient.fetchQuery(userWorkspacesQueryOptions());\n return result.memberships.find((workspace) => workspace.id === workspaceId);\n } catch (error) {\n throw new WorkspaceSetupLoadError(error);\n }\n}\n\nasync function fetchWorkspaceProjectExistence(queryClient: QueryClient, workspaceId: string) {\n const options = projectExistenceQueryOptions(workspaceId);\n\n try {\n return await queryClient.fetchQuery(options);\n } catch (error) {\n const cached = queryClient.getQueryData<{projects: unknown[]}>(options.queryKey);\n if (cached !== undefined) return cached;\n throw new WorkspaceSetupLoadError(error);\n }\n}\n\nasync function fetchWorkspaceSourceConnections(queryClient: QueryClient, workspaceId: string) {\n const options = sourceConnectionsQueryOptions(workspaceId);\n\n try {\n return await queryClient.fetchQuery(options);\n } catch (error) {\n const cached = queryClient.getQueryData<IntegrationConnection[]>(options.queryKey);\n if (cached !== undefined) return cached;\n throw new WorkspaceSetupLoadError(error);\n }\n}\n\nasync function hasHandledModelProviderOnboarding(\n queryClient: QueryClient,\n workspaceId: string,\n): Promise<boolean> {\n if (isModelProviderOnboardingDismissed(workspaceId)) return true;\n\n const options = modelProviderConfigsQueryOptions(workspaceId);\n try {\n const configs = await queryClient.fetchQuery(options);\n return configs.configs.length > 0;\n } catch {\n const cached = queryClient.getQueryData<{configs: unknown[]}>(options.queryKey);\n return cached?.configs.length !== 0;\n }\n}\n\nfunction normalizePath(pathname: string) {\n if (pathname === '/') return pathname;\n return pathname.replace(TRAILING_SLASHES_RE, '');\n}\n\nfunction workspacePath(workspaceId: string, suffix: string) {\n return `/workspaces/${workspaceId}${suffix}`;\n}\n\nfunction isIntegrationsIndexPath(pathname: string, workspaceId: string) {\n return pathname === workspacePath(workspaceId, '/integrations');\n}\n\nfunction isIntegrationSetupPath(pathname: string, workspaceId: string) {\n const basePath = workspacePath(workspaceId, '/integrations');\n return (\n pathname === basePath ||\n pathname.startsWith(`${basePath}/`) ||\n pathname === workspacePath(workspaceId, '/settings/integrations')\n );\n}\n\nfunction isProjectCreationPath(pathname: string, workspaceId: string) {\n return pathname === workspacePath(workspaceId, '/projects/new');\n}\n\nfunction isModelProviderOnboardingPath(pathname: string, workspaceId: string) {\n return pathname === workspacePath(workspaceId, '/model-provider');\n}\n\nfunction isAgentSettingsPath(pathname: string, workspaceId: string) {\n return pathname === workspacePath(workspaceId, '/settings/agents');\n}\n"],"names":["isModelProviderOnboardingDismissed","modelProviderConfigsQueryOptions","sourceConnectionsQueryOptions","projectExistenceQueryOptions","userWorkspacesQueryOptions","WorkspaceSetupLoadError","redirect","TRAILING_SLASHES_RE","loadWorkspaceSetupRoute","queryClient","workspaceId","pathname","workspace","fetchWorkspaceSummary","workspaceStatus","status","undefined","hideProjectNavigation","unavailable","assertNever","projects","fetchWorkspaceProjectExistence","normalizedPathname","normalizePath","length","isIntegrationsIndexPath","to","params","wid","replace","sourceConnections","fetchWorkspaceSourceConnections","hasSourceConnection","isIntegrationSetupPath","isAgentSettingsPath","providerHandled","hasHandledModelProviderOnboarding","isModelProviderOnboardingPath","isProjectCreationPath","value","Error","JSON","stringify","result","fetchQuery","memberships","find","id","error","options","cached","getQueryData","queryKey","configs","workspacePath","suffix","basePath","startsWith"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/client-onboarding",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "12.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@swc/helpers": "^0.5.17",
|
|
21
|
-
"@shipfox/client-agent": "
|
|
22
|
-
"@shipfox/client-integrations": "
|
|
23
|
-
"@shipfox/client-projects": "
|
|
24
|
-
"@shipfox/client-shell": "
|
|
21
|
+
"@shipfox/client-agent": "12.0.0",
|
|
22
|
+
"@shipfox/client-integrations": "12.0.0",
|
|
23
|
+
"@shipfox/client-projects": "12.0.0",
|
|
24
|
+
"@shipfox/client-shell": "12.0.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"@tanstack/react-query": "^5.101.0",
|
|
@@ -28,6 +28,8 @@ import {act, cleanup, fireEvent, render, screen, waitFor} from '@testing-library
|
|
|
28
28
|
import {loadWorkspaceSetupRoute} from './workspace-setup-route.js';
|
|
29
29
|
|
|
30
30
|
const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
|
|
31
|
+
const MEMBERSHIP_ID = '44444444-4444-4444-8444-444444444444';
|
|
32
|
+
const USER_ID = '55555555-5555-4555-8555-555555555555';
|
|
31
33
|
|
|
32
34
|
function jsonResponse(body: unknown, init: ResponseInit = {}) {
|
|
33
35
|
return new Response(JSON.stringify(body), {
|
|
@@ -80,6 +82,7 @@ interface SetupFetchOptions {
|
|
|
80
82
|
connectionsFail?: boolean;
|
|
81
83
|
providerConfigsFail?: boolean;
|
|
82
84
|
projectsPending?: boolean;
|
|
85
|
+
workspaceStatus?: 'active' | 'suspended' | 'deleted';
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
function setupFetch(options: SetupFetchOptions = {}) {
|
|
@@ -92,10 +95,28 @@ function setupFetch(options: SetupFetchOptions = {}) {
|
|
|
92
95
|
connectionsFail = false,
|
|
93
96
|
providerConfigsFail = false,
|
|
94
97
|
projectsPending = false,
|
|
98
|
+
workspaceStatus = 'active',
|
|
95
99
|
} = options;
|
|
96
100
|
|
|
97
101
|
return vi.fn((input: RequestInfo | URL) => {
|
|
98
102
|
const url = input instanceof Request ? input.url : String(input);
|
|
103
|
+
if (url.endsWith('/workspaces')) {
|
|
104
|
+
return Promise.resolve(
|
|
105
|
+
jsonResponse({
|
|
106
|
+
memberships: [
|
|
107
|
+
{
|
|
108
|
+
id: MEMBERSHIP_ID,
|
|
109
|
+
user_id: USER_ID,
|
|
110
|
+
workspace_id: WORKSPACE_ID,
|
|
111
|
+
workspace_name: 'Workspace',
|
|
112
|
+
workspace_status: workspaceStatus,
|
|
113
|
+
created_at: new Date().toISOString(),
|
|
114
|
+
updated_at: new Date().toISOString(),
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
99
120
|
if (url.includes('/projects?')) {
|
|
100
121
|
if (projectsPending) return new Promise<Response>(() => undefined);
|
|
101
122
|
if (projectsFail) return Promise.resolve(jsonResponse({code: 'server-error'}, {status: 500}));
|
|
@@ -179,7 +200,7 @@ function GuardedRoute({label}: {label: string}) {
|
|
|
179
200
|
<div data-testid="project-navigation">
|
|
180
201
|
{setupState.hideProjectNavigation ? 'hidden' : 'visible'}
|
|
181
202
|
</div>
|
|
182
|
-
<main>{label}</main>
|
|
203
|
+
<main>{setupState.unavailable ? 'Workspace unavailable' : label}</main>
|
|
183
204
|
</>
|
|
184
205
|
);
|
|
185
206
|
}
|
|
@@ -236,6 +257,19 @@ describe('workspace setup route hook', () => {
|
|
|
236
257
|
expect(screen.queryByText('Workspace home')).not.toBeInTheDocument();
|
|
237
258
|
});
|
|
238
259
|
|
|
260
|
+
test.each([
|
|
261
|
+
'suspended',
|
|
262
|
+
'deleted',
|
|
263
|
+
] as const)('stops workspace setup for a %s workspace before loading workspace data', async (workspaceStatus) => {
|
|
264
|
+
const fetchImpl = setupFetch({workspaceStatus, projectsPending: true});
|
|
265
|
+
|
|
266
|
+
renderSetupRoute(`/workspaces/${WORKSPACE_ID}`, fetchImpl);
|
|
267
|
+
|
|
268
|
+
expect(await screen.findByText('Workspace unavailable')).toBeInTheDocument();
|
|
269
|
+
expect(screen.getByTestId('project-navigation')).toHaveTextContent('hidden');
|
|
270
|
+
expect(calledUrls(fetchImpl).some((url) => url.includes('/projects?'))).toBe(false);
|
|
271
|
+
});
|
|
272
|
+
|
|
239
273
|
test('allows normal workspace content and skips source connections when a project exists', async () => {
|
|
240
274
|
const fetchImpl = setupFetch({projects: [projectStub()]});
|
|
241
275
|
|
|
@@ -449,6 +483,23 @@ describe('workspace setup route hook', () => {
|
|
|
449
483
|
let projectAttempts = 0;
|
|
450
484
|
const fetchImpl = vi.fn((input: RequestInfo | URL) => {
|
|
451
485
|
const url = input instanceof Request ? input.url : String(input);
|
|
486
|
+
if (url.endsWith('/workspaces')) {
|
|
487
|
+
return Promise.resolve(
|
|
488
|
+
jsonResponse({
|
|
489
|
+
memberships: [
|
|
490
|
+
{
|
|
491
|
+
id: MEMBERSHIP_ID,
|
|
492
|
+
user_id: USER_ID,
|
|
493
|
+
workspace_id: WORKSPACE_ID,
|
|
494
|
+
workspace_name: 'Workspace',
|
|
495
|
+
workspace_status: 'active',
|
|
496
|
+
created_at: new Date().toISOString(),
|
|
497
|
+
updated_at: new Date().toISOString(),
|
|
498
|
+
},
|
|
499
|
+
],
|
|
500
|
+
}),
|
|
501
|
+
);
|
|
502
|
+
}
|
|
452
503
|
if (url.includes('/projects?')) {
|
|
453
504
|
projectAttempts += 1;
|
|
454
505
|
if (projectAttempts === 1)
|
|
@@ -488,6 +539,23 @@ describe('workspace setup route hook', () => {
|
|
|
488
539
|
const fetchImpl = setupFetch({connections: [sourceConnection()]});
|
|
489
540
|
fetchImpl.mockImplementation((input: RequestInfo | URL) => {
|
|
490
541
|
const url = input instanceof Request ? input.url : String(input);
|
|
542
|
+
if (url.endsWith('/workspaces')) {
|
|
543
|
+
return Promise.resolve(
|
|
544
|
+
jsonResponse({
|
|
545
|
+
memberships: [
|
|
546
|
+
{
|
|
547
|
+
id: MEMBERSHIP_ID,
|
|
548
|
+
user_id: USER_ID,
|
|
549
|
+
workspace_id: WORKSPACE_ID,
|
|
550
|
+
workspace_name: 'Workspace',
|
|
551
|
+
workspace_status: 'active',
|
|
552
|
+
created_at: new Date().toISOString(),
|
|
553
|
+
updated_at: new Date().toISOString(),
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
}),
|
|
557
|
+
);
|
|
558
|
+
}
|
|
491
559
|
if (url.includes('/projects?')) {
|
|
492
560
|
return Promise.resolve(jsonResponse({projects, next_cursor: null}));
|
|
493
561
|
}
|
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
sourceConnectionsQueryOptions,
|
|
8
8
|
} from '@shipfox/client-integrations';
|
|
9
9
|
import {projectExistenceQueryOptions} from '@shipfox/client-projects';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
userWorkspacesQueryOptions,
|
|
12
|
+
WorkspaceSetupLoadError,
|
|
13
|
+
type WorkspaceSetupState,
|
|
14
|
+
} from '@shipfox/client-shell/runtime';
|
|
11
15
|
import type {QueryClient} from '@tanstack/react-query';
|
|
12
16
|
import {redirect} from '@tanstack/react-router';
|
|
13
17
|
|
|
@@ -24,6 +28,19 @@ export async function loadWorkspaceSetupRoute({
|
|
|
24
28
|
workspaceId,
|
|
25
29
|
pathname,
|
|
26
30
|
}: WorkspaceSetupRouteOptions): Promise<WorkspaceSetupState> {
|
|
31
|
+
const workspace = await fetchWorkspaceSummary(queryClient, workspaceId);
|
|
32
|
+
const workspaceStatus = workspace?.status;
|
|
33
|
+
switch (workspaceStatus) {
|
|
34
|
+
case undefined:
|
|
35
|
+
case 'active':
|
|
36
|
+
break;
|
|
37
|
+
case 'suspended':
|
|
38
|
+
case 'deleted':
|
|
39
|
+
return {hideProjectNavigation: true, unavailable: true};
|
|
40
|
+
default:
|
|
41
|
+
return assertNever(workspaceStatus);
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
const projects = await fetchWorkspaceProjectExistence(queryClient, workspaceId);
|
|
28
45
|
const normalizedPathname = normalizePath(pathname);
|
|
29
46
|
|
|
@@ -82,6 +99,19 @@ export async function loadWorkspaceSetupRoute({
|
|
|
82
99
|
});
|
|
83
100
|
}
|
|
84
101
|
|
|
102
|
+
function assertNever(value: never): never {
|
|
103
|
+
throw new Error(`Unhandled workspace status: ${JSON.stringify(value)}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function fetchWorkspaceSummary(queryClient: QueryClient, workspaceId: string) {
|
|
107
|
+
try {
|
|
108
|
+
const result = await queryClient.fetchQuery(userWorkspacesQueryOptions());
|
|
109
|
+
return result.memberships.find((workspace) => workspace.id === workspaceId);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new WorkspaceSetupLoadError(error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
85
115
|
async function fetchWorkspaceProjectExistence(queryClient: QueryClient, workspaceId: string) {
|
|
86
116
|
const options = projectExistenceQueryOptions(workspaceId);
|
|
87
117
|
|