Skip to content

YouTube Import: Long-Running Task UX — Progress Tracking + Status Polling #130

@davidortinau

Description

@davidortinau

Overview

Implement the client-side UX for tracking long-running video imports. The pattern is: submit → poll → display status → notify on completion.

Crew: Kaylee (UI) + Wash (API endpoints)
Depends on: #126 (data model), #128 (worker), #129 (UI pages)

Decision: Polling over SignalR

Why polling wins for this app:

  1. Blazor Hybrid on mobile: WebView pauses on backgrounding, killing SignalR connections
  2. Import pipeline takes 15-60 seconds — not a latency-sensitive operation
  3. Polling is simpler to implement, debug, and maintain
  4. Works identically on both MAUI (Blazor Hybrid) and web (Blazor Server)

Polling Behavior

On the Import page:

  • Poll GET /api/import?status=active every 5 seconds while page is visible
  • "Active" = Queued + FetchingTranscript + PolishingTranscript + ExtractingVocabulary
  • Stop polling when no active imports remain
  • Use System.Threading.Timer in the Blazor component with IDisposable cleanup

On other pages (optional, phase 2):

  • Poll every 30 seconds for a count of active imports
  • Display badge on Import nav item if count > 0
  • Lightweight: GET /api/import/active-count returns just an integer

On completion:

  • Toast notification: "Video imported: {title}"
  • Import row updates to show "Complete" with link to resource
  • If the created resource has vocabulary, show word count: "22 words extracted"

On failure:

  • Toast notification: "Import failed: {title}"
  • Import row shows error message + "Retry" button
  • Retry = POST /api/import/{id}/retry → resets status to Queued

API Endpoints (Wash)

GET  /api/import                 — List imports (filterable by status)
GET  /api/import/active-count    — Count of non-terminal imports (for badge)
POST /api/import/{id}/retry      — Reset failed import to Queued

Response shape:

{
  "id": "abc-123",
  "videoUrl": "https://youtube.com/watch?v=xyz",
  "title": "Korean Street Food Tour",
  "status": "ExtractingVocabulary",
  "errorMessage": null,
  "learningResourceId": null,
  "channelName": "Korean Englishman",
  "createdAt": "2026-07-22T10:30:00Z",
  "updatedAt": "2026-07-22T10:30:45Z"
}

Blazor Component Pattern

@implements IDisposable

private Timer? _pollTimer;
private List<VideoImportDto> _activeImports = new();

protected override void OnInitialized()
{
    _pollTimer = new Timer(async _ =>
    {
        var imports = await Http.GetFromJsonAsync<List<VideoImportDto>>("/api/import?status=active");
        if (imports != null)
        {
            // Check for newly completed imports → show toast
            foreach (var completed in _activeImports
                .Where(old => imports.Any(i => i.Id == old.Id && i.Status == "Complete")))
            {
                Toast.ShowSuccess($"Imported: {completed.Title}");
            }
            _activeImports = imports;
            await InvokeAsync(StateHasChanged);
        }

        // Stop polling if nothing active
        if (!_activeImports.Any())
            _pollTimer?.Change(Timeout.Infinite, Timeout.Infinite);

    }, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
}

public void Dispose() => _pollTimer?.Dispose();

Tasks

  • Create VideoImportDto in Contracts project
  • Add API endpoints: list imports, active count, retry
  • Implement polling timer in Import.razor component
  • Status display component (icon + label per status)
  • Completion toast notifications
  • Failure display with retry button
  • Timer cleanup on component dispose
  • Test: submit import → observe status changes → completion toast
  • (Phase 2) Nav badge for active import count

Architecture Reference

See .squad/decisions/inbox/zoe-youtube-import-architecture.md — Section 3

Metadata

Metadata

Assignees

No one assigned

    Labels

    go:needs-researchNeeds investigationsquadSquad triage inbox — Lead will assign to a membersquad:washAssigned to Wash (Backend Dev)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions