The TransferProgressCardComponent is a universal component that handles transfer progress tracking for all transfer plugins (Globus and non-Globus).
The component requires an explicit isGlobus input from the parent component to determine the transfer mode. This avoids relying on global state and ensures correct behavior regardless of how the user navigated to the page.
@Input({ required: true })
isGlobus = false; // Must be set by parent: submit or download componentWhy explicit input instead of auto-detection?
- Download component can be accessed directly (bypassing connect page)
- Global
credentialsService.credentials.pluginmay not reflect actual transfer mode - Parent component knows the context better than shared child component
Input Requirements:
isGlobus:true(required)taskId: Globus task ID (returned by backend)monitorUrl: (optional) Globus monitor URL
Polling Behavior:
- Polls
submitService.getGlobusTransferStatus(taskId)every 5 seconds - Returns real-time Globus transfer status from Globus API
- Shows Globus-specific messages ("Checking Globus transfer status…")
- Links to Globus web interface for detailed monitoring
Data Flow:
Globus API → submitService → TransferTaskStatus → UI
Input Requirements:
isGlobus:false(required)taskId: Dataset ID (used as identifier, not a real "task")data: CompareResult containing file listdataUpdate: Callback to update parent component's data
Polling Behavior:
- Polls
dataUpdatesService.updateData(files, datasetId)every 5 seconds - Backend checks actual file statuses in the repository
- Maps file statuses to TransferTaskStatus format
- Updates parent component via
dataUpdatecallback - Shows generic messages ("Checking transfer status…")
Data Flow:
Backend Polling → CompareResult → buildStatusFromCompareResult() → TransferTaskStatus → UI
↓
dataUpdate callback → Parent Component
The component maps CompareResult data to unified TransferTaskStatus:
| CompareResult Status | Mapped Status | Message |
|---|---|---|
ResultStatus.Finished |
SUCCEEDED |
"Transfer completed successfully." |
ResultStatus.Updating |
ACTIVE |
"Transfer in progress…" |
ResultStatus.New |
PENDING |
"Preparing transfer…" |
| Other | ACTIVE |
"Waiting for repository updates…" |
| File Status | Counted As |
|---|---|
Filestatus.Equal |
Completed (transferred) |
Filestatus.Deleted |
Skipped |
Filestatus.Unknown |
Failed |
// Works for ALL plugins
transferTaskId?: string | null; // Globus task ID or dataset ID
transferMonitorUrl?: string | null; // External URL (Globus only)
transferInProgress = false; // Polling stateif (this.isGlobus()) {
// Use Globus task ID from backend response
this.transferTaskId = data.globusTransferTaskId ?? null;
this.transferMonitorUrl = data.globusTransferMonitorUrl ?? null;
} else {
// Use dataset ID for backend polling
this.transferTaskId = this.pid;
}
this.transferInProgress = true;<app-transfer-progress-card [isGlobus]="isGlobus()" [taskId]="transferTaskId" [monitorUrl]="transferMonitorUrl" [submitting]="transferInProgress && !transferTaskId" (pollingChange)="onStatusPollingChange($event)" [data]="compareResult" [dataUpdate]="onDataUpdate.bind(this)" (completed)="done = true"></app-transfer-progress-card>-
isGlobus: Transfer mode selector (required)true→ Globus API pollingfalse→ Backend polling with file status mapping
-
taskId: The identifier for polling- Globus: actual Globus task ID
- Others: dataset ID (PID)
-
data: CompareResult with file list (non-Globus only)- Contains current file states
- Updated by card's polling → triggers
dataUpdatecallback
-
dataUpdate: Callback to sync parent's data (non-Globus only)- Card polls backend → gets updated CompareResult
- Calls this callback → parent updates its file list UI
-
monitorUrl: External monitor link (Globus only)- If provided: used as-is
- If not: constructed from taskId
Download component always uses Globus, so it's simpler:
<app-transfer-progress-card [isGlobus]="true" [taskId]="lastTransferTaskId" [monitorUrl]="globusMonitorUrl" [submitting]="downloadInProgress && !lastTransferTaskId" (pollingChange)="onStatusPollingChange($event)"></app-transfer-progress-card>Note:
isGlobusis hardcoded totrue(download always uses Globus)- No
dataordataUpdateneeded because download is always Globus
- Single Component for All Plugins: Reduces code duplication and ensures consistent UX
- Explicit Mode Selection: Parent component passes
isGlobusflag to avoid global state dependency - Unified Status Model: Both paths produce
TransferTaskStatusfor consistent UI - Generic Naming: Properties named for function, not specific plugin (e.g.,
transferTaskIdnotglobusTaskId) - Conditional Messages: UI text adapts based on plugin ("Globus" vs generic)
- No Implicit Assumptions: Component doesn't guess plugin type from navigation history
✅ Consistent UX: Same progress card for all transfers ✅ Maintainable: One component to update, not multiple variants ✅ Flexible: Easy to add new plugin types ✅ Clear Separation: Plugin-specific logic contained in card ✅ Type-Safe: TypeScript enforces correct data flow ✅ Reliable: No dependency on global state or navigation history ✅ Explicit: Parent controls behavior through clear input contract
To add a new transfer plugin:
Option 1: Keep binary mode (if new plugin is non-Globus)
- No changes needed! New plugin uses
isGlobus=falsepath - Works exactly like existing non-Globus plugins
Option 2: Add specific plugin type (if new plugin needs different behavior)
- Change
isGlobusinput topluginType: 'globus' | 'backend' | 'newtype' - Add case in
getTransferStatus()for new plugin's polling method - Add case in error messages if plugin needs specific messaging
- Update submit component to pass correct plugin type
- Download remains
pluginType='globus'(if unchanged)