Skip to content

✨ Tiger Portal — transcript upload, per-user dashboard history, SWA login#147

Draft
yaqi-lyu wants to merge 8 commits into
masterfrom
portal-transcript
Draft

✨ Tiger Portal — transcript upload, per-user dashboard history, SWA login#147
yaqi-lyu wants to merge 8 commits into
masterfrom
portal-transcript

Conversation

@yaqi-lyu

@yaqi-lyu yaqi-lyu commented Jul 17, 2026

Copy link
Copy Markdown
Member

Addresses #137

Summary

Adds the Parrot portal — a self-serve entry point where an SSW user signs in and either uploads a Teams .vtt transcript or pastes a Teams meeting link, then gets a generated Tiger dashboard, with a per-user history of their submissions and their status.

Both paths flow through the existing queue → Container App Job → dashboard pipeline via a sourceType discriminated union, so no separate processing path was introduced and the automated Graph webhook path is untouched.

Architecture

  • Portal SPA (portal/) — React 19 + Vite, styled with Tailwind using the same SSW design tokens as the generated dashboards. Views: Submit (Upload transcript / Paste meeting link) + "My dashboards".
  • Portal API (portal-api/) — a separate Azure Functions app (func-tiger-portal-*), split from the Graph webhook app so its SWA-only auth boundary never touches the public webhook. Endpoints: POST /api/v1/submissions (file upload), POST /api/v1/meetings (meeting link), GET /api/v1/submissions (per-user history). Stays Graph-free — the meeting-link endpoint only validates the link format; Graph resolution happens in the Job.
  • Login — Azure Static Web Apps built-in Entra (AAD) auth, tenant-locked to SSW, reusing the existing Graph app registration (no new registration). The API is authLevel: anonymous; the trust boundary is the auto-provisioned "Azure Static Web Apps (Linked)" EasyAuth provider (verified post-deploy).
  • Meeting-link resolutionprocessor/downloadFromMeetingLink.js runs in the Container App Job (which already has Graph creds), resolving a join URL → organizer's onlineMeetings → latest transcript → VTT. The organizer/tenant come from the link's own context param, so a join link alone is sufficient.
  • Persistence — a Cosmos submissions container (partitioned by /projectName) holds {owner, status, dashboardUrl, …}. The Portal API writes the record on submit; the processor patches status/dashboardUrl on completion.

Acceptance criteria (from #137)

# Criterion Status
1 Visible menu item to submit content ✅ Submit view + nav
2 Supports providing a meeting link ✅ "Paste meeting link" mode
3 Supports providing a meeting transcript .vtt upload
4 Valid meeting link creates a request ✅ link → meetingLink job
5 Valid transcript creates a request ✅ upload → uploadedTranscript job
6 Confirmation that the request was accepted ✅ 202 + success state + reference ID
7 Processing status is visible ✅ "My dashboards" (Queued / Processing / Ready / Failed)
8 Link to the dashboard on completion ✅ "Open dashboard" on completed rows
9 Authorization prevents unauthorized processing/viewing ⚠️ Partial — tenant-locked login + per-user list isolation. Any authenticated SSW user with a valid meeting link can process it (stricter than the existing anonymous TriggerProcessing); participant-level restriction is a follow-up. Report-body isolation is intentionally deferred: dashboards remain public shareable URLs so users can send them to attendees.

Deploy notes

  • Gated behind deployPortal=false (default) — existing deployments are unaffected until explicitly enabled.
  • SWA requires the Standard plan and a supported region (australiaeast is not one; defaults to East Asia).
  • Post-deploy is not optional — run infra/scripts/portal-post-deploy.sh <env>:
    1. Verifies the Azure Static Web Apps (Linked) EasyAuth provider exists on the Function App — this is what makes the anonymous API safe against forged identity headers.
    2. Registers the .auth/login/aad/callback redirect URI on the Graph app registration.
    3. Reminders: enable user sign-in on the Graph reg, grab the SWA deploy token.
  • Set the SSW_TENANT_ID repo variable — the SWA deploy workflow substitutes it into staticwebapp.config.json and fails loudly if it's missing.
  • Meeting-link processing relies on the Teams application access policy already configured for the Graph app (the same one TriggerProcessing uses).

Testing

  • portal-api 26, azure-function 4, portal 12, processor (upload + meeting-link) 7 — all green; portal builds; bicep compiles; entrypoint.sh passes bash -n.
  • Biome config added; lint fixed on PR-touched files.
  • A high-effort multi-agent review was run and triaged.
  • Not yet done: end-to-end run against a live Cosmos/SWA/Graph environment.

Follow-ups

  • Participant-level authorization for meeting-link submissions (tighten AC 9).
  • Report-body isolation, if reports must not be publicly shareable.
  • Reconciliation sweep for the rare "hard process kill leaves a stuck accepted record" window.
  • Repo-wide Biome format sweep (config landed this PR; existing code not yet reformatted).

🤖 Generated with Claude Code

yaqi-lyu and others added 8 commits July 17, 2026 14:55
- Untrack .claude/launch.json (local editor artifact) and gitignore it.
- Remove @azure/storage-blob/@azure/storage-queue from azure-function:
  the code using them moved to portal-api, so they were dead deps. This
  reverts azure-function/package-lock.json back to master (removes ~230
  lines of lockfile churn from the PR).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- node: import protocol + optional chaining + import-type (mechanical).
- Suppress intentional control-char filename regex and the drag-drop
  static-element a11y rule, each with a documented reason.
- "Transcript file" label → <p> (the dropzone input carries its own label).

Left untouched: pre-existing noUnusedVariables in ProcessTranscriptQueue.js
and projectSetup.js (predate this PR) and idiomatic non-null assertions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds AC #2/#4 from #137 — a second submission mode alongside file upload.

- portal: UploadView gains an "Upload transcript | Paste meeting link"
  toggle; SubmissionClient.submitLink() POSTs JSON to /api/v1/meetings.
- portal-api: SubmitMeetingLink (POST /api/v1/meetings) validates the link
  format (parses context/Oid - no Graph call), writes the shared history
  record, and enqueues a `meetingLink` message. Stays Graph-free.
- azure-function: queueMessage normalizes/deduplicates/env-maps `meetingLink`.
- processor: downloadFromMeetingLink.js resolves joinUrl to the organizer's
  onlineMeetings, latest transcript, VTT - in the Job (which already has
  Graph creds). Reuses validateDownloadedVtt + detectVttSpeakers.
- entrypoint.sh: PORTAL_SUBMISSION covers upload + meetingLink (shared
  history write-back, Logic App notifications suppressed). Webhook untouched.

Authorization: any authenticated SSW-tenant user with a valid link can
process it; participant-level restriction is a future tightening.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes deploy-portal-api-test.yml and deploy-portal-swa-test.yml. The
existing func deploy job and manual runs cover deployment; these can be
re-added later.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant