Skip to content

fix(course-optimizer): fix start-analysis run-id assumption, polling, and not-started copy - #114

Open
nsprenkle wants to merge 5 commits into
release-ulmofrom
nsprenkle/course-optimizer-async-analysis
Open

fix(course-optimizer): fix start-analysis run-id assumption, polling, and not-started copy#114
nsprenkle wants to merge 5 commits into
release-ulmofrom
nsprenkle/course-optimizer-async-analysis

Conversation

@nsprenkle

@nsprenkle nsprenkle commented Sep 9, 2026

Copy link
Copy Markdown
Member

Description

Follow-up to #109. Three small fixes to the Course Optimizer extended report's start/poll cycle, found while testing #109 end-to-end against a devstack:

1. Stop expecting a run id from the start-analysis POST

In combination with edx/edx-platform#466 (which moved course export / compress to an async task), Studio's POST endpoint now returns 202 {"status": "pending"} immediately, before a run exists, rather than proxying the backend's real {run_id, ...} response synchronously.

This updates postCourseAnalysisReport to return void instead of { runId }, since nothing here actually consumed that value -- CourseOptimizerExtendedPage only ever used isPending/isError from the mutation.

2. Stop polling analysis status when no run exists

useCourseOptimizerReport's refetchInterval used to treat a null result (course has no run yet -- Studio proxies a 404 in that case) the same as an actively-pending run, and polled every 2s indefinitely -- hammering Studio with repeated 404s for a course whose analysis was never started. It now only polls while a run actually exists and is in an active status (PENDING/RUNNING/PARTIAL).

This is a real behavior change from what #109 originally shipped (and from what this PR's own first commit above assumed): starting a run in the same browser tab still works immediately, since useStartCourseAnalysisReport's onSuccess explicitly invalidates the query rather than relying on the interval. The one tradeoff: a run started in a different tab/session for the same course won't be picked up here until the page is reloaded, since polling no longer runs speculatively while there's no run to check on.

3. Clarify the never-scanned state vs. a load error

The "no run yet" state was a single muted line of body text with no heading (notStartedHeading was defined in messages.ts but never actually rendered), easy to mistake for an error state at a glance. Now renders a clear heading ("No previous scans found for this course") and points directly at the scan button, kept visually distinct from the real load-error state (isError).

Supporting information

Companion PR: edx/edx-platform#466

Testing instructions

  1. npm test -- src/optimizer-page should pass.
  2. npm run types should pass.
  3. Manually, with both companion PRs applied: for a course with no prior run, confirm the status endpoint is called once (not every 2s). Click "Start analysis" -- it should transition to Running (polling resumes at ~2s) and eventually show the full report, then stop polling once Complete.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Polling can stop after an initial 404 before the asynchronously created run becomes available.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Updates the Course Optimizer frontend for Studio’s asynchronous 202 Accepted analysis-start response.

Changes:

  • Removes the unused run ID from the POST API.
  • Updates polling, invalidation, and related tests.
  • Revises pre-analysis messaging and headings.
File summaries
File Summary
src/optimizer-page/extended/messages.ts Updates no-run messaging.
src/optimizer-page/extended/data/apiHooks.ts Adjusts asynchronous startup polling and invalidation.
src/optimizer-page/extended/data/apiHooks.test.tsx Updates polling and mutation tests.
src/optimizer-page/extended/data/api.ts Makes the start API return void.
src/optimizer-page/extended/data/api.test.ts Updates POST API tests.
src/optimizer-page/extended/CourseOptimizerReportBody.tsx Adds the no-run heading.
src/optimizer-page/extended/CourseOptimizerReportBody.test.tsx Verifies updated no-run content.
src/optimizer-page/CourseOptimizerExtendedPage.test.tsx Updates queued-start test fixtures.
Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/optimizer-page/extended/data/apiHooks.ts
nsprenkle and others added 3 commits September 11, 2026 11:14
…alysis POST

The edx-platform companion PR moves the course export + upload to
xpert-ai-workflows into a background Celery task, since it could take
long enough on large courses to risk a proxy/gateway timeout. Studio's
POST endpoint now returns 202 immediately (before a run exists) instead
of proxying the backend's real response, so it can no longer hand back
a run id synchronously.

Nothing in the UI actually consumed the returned runId -- the page only
relies on isPending/isError plus the existing status poll, which already
treats "no run yet" as still-pending -- so this is a type/response-shape
cleanup with no behavior change for users.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
useCourseOptimizerReport treated a 404 (course has no run yet) as
"pending" and kept polling every 2s indefinitely, hammering Studio with
repeated 404s for a course whose analysis was never started. Poll only
while a real run is actively in progress -- starting one still updates
immediately via useStartCourseAnalysisReport's own invalidateQueries
call, so nothing relied on the removed fallback polling.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
notStartedHeading was defined but never rendered, leaving the "no run
yet" state as a single muted line with no explicit heading -- easy to
mistake for an error state at a glance despite already being handled as
a distinct case (isError vs. a null run). Render the heading, and reword
both messages to name the state and point at the scan button directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@nsprenkle
nsprenkle force-pushed the nsprenkle/course-optimizer-async-analysis branch from 4c20d56 to 64213e9 Compare September 11, 2026 15:14
@nsprenkle nsprenkle changed the title fix(course-optimizer): stop expecting a run id from the start-analysis POST fix(course-optimizer): fix start-analysis run-id assumption, polling, and not-started copy Sep 11, 2026
nsprenkle and others added 2 commits September 11, 2026 11:55
…t race

The start-analysis POST only queues a background export/upload task
(edx-platform#466) -- the run isn't visible to the status endpoint the
instant it resolves. Combined with the prior commit's fix to stop
polling on a null result, a still-null refetch right after starting a
run could get stuck in the not-started state forever, since nothing
would trigger another check.

useCourseOptimizerReport now takes an `awaitingRun` flag (true from a
successful start until a run appears) so it keeps polling through that
gap without reintroducing indefinite polling for a course that was
never scanned.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
edx-platform#466 (ea9a0d03c6) now marks a just-started run PENDING in
its own cache synchronously before the start POST returns, so the
status endpoint never actually returns null right after a start
succeeds -- the race this commit's awaitingRun flag was bridging can no
longer happen. Reverts to the simpler, already-correct polling logic
rather than keeping defensive client-side coordination for a gap that's
now closed at the source.

Co-Authored-By: Claude Sonnet 5 <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.

2 participants