|
1 | 1 | import classNames from 'classnames'; |
| 2 | +import { formatDistanceToNowStrict } from 'date-fns'; |
2 | 3 | import React, { type FC, Fragment, type ReactNode, useEffect, useId, useMemo, useRef, useState } from 'react'; |
3 | 4 | import { type DirectoryDropItem, type FileDropItem, OverlayContainer, useDrop } from 'react-aria'; |
4 | 5 | import { Heading } from 'react-aria-components'; |
5 | | -import { useNavigate } from 'react-router'; |
| 6 | +import { useNavigate, useParams } from 'react-router'; |
6 | 7 |
|
| 8 | +import { isNotNullOrUndefined } from '~/common/misc'; |
7 | 9 | import { scopeToActivity } from '~/models/workspace'; |
8 | 10 | import { useImportResourcesFetcher } from '~/routes/import.resources'; |
9 | 11 | import { useScanResourcesFetcher } from '~/routes/import.scan'; |
| 12 | +import { useProjectListWorkspacesLoaderFetcher } from '~/routes/organization.$organizationId.project.$projectId.list-workspaces'; |
10 | 13 | import { Checkbox } from '~/ui/components/base/checkbox'; |
11 | 14 |
|
12 | 15 | import type { ScanResult } from '../../../../common/import'; |
@@ -217,12 +220,21 @@ export const ImportModal: FC<ImportModalProps> = ({ |
217 | 220 | requests: scanResourcesFetcherData.map(scanResult => scanResult.requests?.length || 0), |
218 | 221 | }, |
219 | 222 | }); |
220 | | - const workspace = importFetcher?.data?.workspace; |
221 | | - workspace |
222 | | - ? navigate( |
223 | | - `/organization/${organizationId}/project/${defaultProjectId}/workspace/${workspace._id}/${scopeToActivity(workspace.scope)}`, |
224 | | - ) |
225 | | - : navigate(`/organization/${organizationId}/project/${defaultProjectId}`); |
| 223 | + const workspace = importFetcher?.data?.singleImportedWorkspace; |
| 224 | + const request = importFetcher?.data?.singleImportedRequest; |
| 225 | + if (workspace && request) { |
| 226 | + navigate( |
| 227 | + `/organization/${organizationId}/project/${defaultProjectId}/workspace/${workspace._id}/debug/request/${request._id}`, |
| 228 | + ); |
| 229 | + return modalRef.current?.hide(); |
| 230 | + } |
| 231 | + if (workspace) { |
| 232 | + navigate( |
| 233 | + `/organization/${organizationId}/project/${defaultProjectId}/workspace/${workspace._id}/${scopeToActivity(workspace.scope)}`, |
| 234 | + ); |
| 235 | + return modalRef.current?.hide(); |
| 236 | + } |
| 237 | + navigate(`/organization/${organizationId}/project/${defaultProjectId}`); |
226 | 238 | modalRef.current?.hide(); |
227 | 239 | } |
228 | 240 | }, [defaultProjectId, defaultWorkspaceId, importFetcher?.data, navigate, organizationId, scanResourcesFetcherData]); |
@@ -276,13 +288,13 @@ export const ImportModal: FC<ImportModalProps> = ({ |
276 | 288 | loading={importFetcher.state !== 'idle'} |
277 | 289 | disabled={importErrors.length > 0} |
278 | 290 | isImportingBaseEnvironmentToWorkspace={!!isImportingBaseEnvironmentToWorkspace} |
279 | | - onImport={(overrideBaseEnvironmentData: boolean) => { |
| 291 | + onImport={(overrideBaseEnvironmentData: boolean, selectedWorkspaceId?: string) => { |
280 | 292 | invariant(Array.isArray(scanResourcesFetcherData)); |
281 | 293 |
|
282 | 294 | importFetcher.submit({ |
283 | 295 | organizationId, |
284 | 296 | projectId: defaultProjectId || '', |
285 | | - workspaceId: shouldImportToWorkspace ? defaultWorkspaceId : undefined, |
| 297 | + workspaceId: selectedWorkspaceId || (shouldImportToWorkspace ? defaultWorkspaceId : undefined), |
286 | 298 | options: { |
287 | 299 | overrideBaseEnvironmentData, |
288 | 300 | }, |
@@ -419,17 +431,65 @@ const ImportResourcesForm = ({ |
419 | 431 | }: { |
420 | 432 | scanResults: ScanResult[]; |
421 | 433 | errors?: string[]; |
422 | | - onImport: (overrideBaseEnvironmentData: boolean) => void; |
| 434 | + onImport: (overrideBaseEnvironmentData: boolean, selectedWorkspaceId?: string) => void; |
423 | 435 | disabled: boolean; |
424 | 436 | loading: boolean; |
425 | 437 | isImportingBaseEnvironmentToWorkspace: boolean; |
426 | 438 | }) => { |
| 439 | + const { organizationId, projectId, workspaceId } = useParams() as { |
| 440 | + organizationId: string; |
| 441 | + projectId: string; |
| 442 | + workspaceId: string; |
| 443 | + }; |
427 | 444 | const [overrideBaseEnvironmentData, setOverrideBaseEnvironmentData] = useState(true); |
| 445 | + const isSingleRequest = scanResults.length === 1 && (scanResults[0].requests?.length || 0) === 1; |
| 446 | + const workspacesFetcher = useProjectListWorkspacesLoaderFetcher(); |
| 447 | + const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(workspaceId || ''); |
| 448 | + useEffect(() => { |
| 449 | + const isIdleAndUninitialized = workspacesFetcher.state === 'idle' && !workspacesFetcher.data; |
| 450 | + if (isIdleAndUninitialized) { |
| 451 | + workspacesFetcher.load({ |
| 452 | + organizationId, |
| 453 | + projectId, |
| 454 | + }); |
| 455 | + } |
| 456 | + }, [organizationId, projectId, workspacesFetcher]); |
| 457 | + // List collections for active project, sorted by last modified timestamp descending |
| 458 | + // Should we list design or mcp? |
| 459 | + const workspacesForActiveProject = |
| 460 | + workspacesFetcher?.data?.files |
| 461 | + .toSorted((a, b) => b.lastModifiedTimestamp - a.lastModifiedTimestamp) |
| 462 | + .map(w => ({ ...w.workspace, lastModifiedTimestamp: w.lastModifiedTimestamp })) |
| 463 | + .filter(isNotNullOrUndefined) |
| 464 | + .filter(w => w.scope === 'collection' || w.scope === 'design') || []; |
| 465 | + const shouldShowWorkspaceSelect = !workspaceId && isSingleRequest && workspacesForActiveProject.length > 0; |
428 | 466 | return ( |
429 | 467 | <Fragment> |
430 | 468 | <div className="flex max-h-[50vh] flex-col gap-(--padding-md) overflow-auto"> |
431 | 469 | <div className="overflow-y-auto"> |
432 | 470 | <ScanResultsTable scanResults={scanResults} /> |
| 471 | + {shouldShowWorkspaceSelect && ( |
| 472 | + <div className="form-row mt-2"> |
| 473 | + <div className="form-control form-control--outlined"> |
| 474 | + <label> |
| 475 | + Select Workspace: |
| 476 | + <select |
| 477 | + aria-label="Select Workspace" |
| 478 | + name="workspaceId" |
| 479 | + value={selectedWorkspaceId} |
| 480 | + onChange={e => setSelectedWorkspaceId(e.target.value)} |
| 481 | + > |
| 482 | + <option value="">-- New Workspace --</option> |
| 483 | + {workspacesForActiveProject.map(w => ( |
| 484 | + <option key={w._id} value={w._id}> |
| 485 | + {w.name} - {formatDistanceToNowStrict(w.lastModifiedTimestamp)} |
| 486 | + </option> |
| 487 | + ))} |
| 488 | + </select> |
| 489 | + </label> |
| 490 | + </div> |
| 491 | + </div> |
| 492 | + )} |
433 | 493 | {isImportingBaseEnvironmentToWorkspace && ( |
434 | 494 | <Checkbox |
435 | 495 | isSelected={overrideBaseEnvironmentData} |
@@ -462,7 +522,7 @@ const ImportResourcesForm = ({ |
462 | 522 | variant="contained" |
463 | 523 | bg="surprise" |
464 | 524 | disabled={disabled} |
465 | | - onClick={() => onImport(overrideBaseEnvironmentData)} |
| 525 | + onClick={() => onImport(overrideBaseEnvironmentData, selectedWorkspaceId)} |
466 | 526 | className="btn h-10 gap-(--padding-sm)" |
467 | 527 | > |
468 | 528 | {loading ? ( |
|
0 commit comments