Skip to content

feat(b2b_dashboard): filter learner-progress by course run - #79

Merged
daniellefrappier18 merged 5 commits into
mainfrom
daniellef/b2b-course-module-filter
Sep 30, 2026
Merged

daniellefrappier18 merged 5 commits into
mainfrom
daniellef/b2b-course-module-filter

Conversation

@daniellefrappier18

Copy link
Copy Markdown
Contributor

What are the relevant tickets?

N/A

Description (What does it do?)

  • Adds an optional courserun_readable_id query param to GET .../learner-progress, so the endpoint can narrow to one course run instead of silently dropping the param.
  • Adds a new GET .../organizations/{organization_id}/contracts/{contract_id}/course-runs endpoint listing the distinct course runs under a contract (id, title, start/end dates), for populating a course dropdown.
  • Regenerates openapi/specs/b2b_dashboard.yaml for both changes.

This unblocks the "Module" filter already built (and currently disabled) in mit-learn's ContractLearnersPage.tsx — its own comment there says it sends courserun_readable_id, which this API previously ignored rather than rejected.

Implementation details
  • learner_queries.py: ProgressFilters gains courserun_readable_id: str | None; learner_progress() appends an exact-match predicate the same way search does today. A new course_runs() query reads mv_b2b_contract_courserun, scoped to sso_organization_id/contract_id — the same MV the separate b2b_learner_records tenant already reads for its own /courses endpoint (both tenants' configs default to the same physical schema, b2b_learner_records). No consent gating or anonymization floor on this query: it's course-run catalog metadata, not learner rows.
  • learner_models.py: new CourseRun and CourseRunsResponse models, matching the field-description conventions of the existing LearnerProgress/LearnerProgressResponse models (manager-facing text, no field names in descriptions).
  • routers/learners.py: new courserun_readable_id query param on learner_progress, and a new course_runs route, reusing the router's existing require_org_manager/require_contract_in_org dependencies and the existing Pagination dependency.

Screenshots (if appropriate):

N/A — API-only change, no UI in this repo.

How can this be tested?

  • uv run pytest tests/test_dashboard_learner_progress.py tests/test_dashboard_course_runs.py -q — 29 tests, including a new case asserting courserun_readable_id = %s is bound into both the page and count queries when passed and omitted otherwise, and 7 new tests for course-runs (envelope shape, org/contract scoping, empty-contract case, null-start-date sort order, auth gates, field descriptions).
  • uv run pytest tests/ -k "dashboard or openapi" -q — 39 passed.
  • uv run ruff check and uv run mypy clean on all touched files.
  • uv run bin/generate-openapi-spec --check — confirmed the committed spec is current; the diff is additive only (new optional param, new endpoint/schemas), verified via git diff openapi/specs/b2b_dashboard.yaml.
  • Not tested against a live StarRocks cluster — no environment with real mv_b2b_contract_courserun/mv_b2b_learner_enrollment data was available locally.

Additional Context

Tracked as witan project wp-add-course-module-filter-to-b2b-learner-director-bfe2ef. Follow-up in mit-learn to re-enable ContractLearnersPage.tsx's commented-out module filter, and to make each row in the contract analytics "Course performance" table (enrollment-funnel endpoint) link to the learner directory pre-filtered by that row's courserun_readable_id, is not part of this PR.

Adds an optional courserun_readable_id filter to the learner-progress
endpoint, and a new course-runs endpoint listing a contract's course runs
(reads mv_b2b_contract_courserun, already exposed by the b2b_learner_records
tenant). Together these let the frontend's already-built module filter on
the contract learner directory actually filter, and populate its dropdown.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

OpenAPI Changes

Show/hide changes
## Changes for b2b_dashboard.yaml:
2 changes: 0 error, 0 warning, 2 info
info	[endpoint-added] at head/openapi/specs/b2b_dashboard.yaml
	in API GET /api/v1/analytics/organizations/{organization_id}/contracts/{contract_id}/course-runs
		endpoint added

info	[new-optional-request-parameter] at head/openapi/specs/b2b_dashboard.yaml
	in API GET /api/v1/analytics/organizations/{organization_id}/contracts/{contract_id}/learner-progress
		added the new optional `query` request parameter `courserun_readable_id`



## Changes for b2b_learner_records.yaml:
No changes detected

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

This comment was marked as outdated.

Reject an explicit empty courserun_readable_id instead of silently
treating it as "no filter" (a truthy check let "" fall through). Make
course_runs() pagination deterministic by tie-breaking on the unique
courserun_readable_id, since courserun_title can repeat across runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

This comment was marked as outdated.

Picks up the minLength: 1 constraint added to courserun_readable_id.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

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.

Copilot review overview

🟢 Approval recommended

The implementation is scoped, authorized, deterministically paginated, documented, and adequately tested.

Review effort: Balanced
Findings: None

Resolved since last review (1)

@daniellefrappier18
daniellefrappier18 merged commit f627b55 into main Sep 30, 2026
5 checks passed
@daniellefrappier18
daniellefrappier18 deleted the daniellef/b2b-course-module-filter branch September 30, 2026 13:43
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.

3 participants