Feat/provider platform rebased#1165
Conversation
- user_matching.go: types, levenshtein, matchUsers, handleMatchUsers, handleApplyMatches - GetAllUnmappedUsers DB function (no pagination) - match-users and apply-matches routes registered Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…te for name matching
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lient recreation functionality Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…es and ensuring user data is correctly mapped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Replaces total_students (raw Canvas count) with a count of users who have a ProviderUserMapping in UnlockEd. handleGetCanvasClassDetail makes one extra enrollment call; handleGetCanvasClasses fans out concurrently via goroutines. The enrollment list handler was already correct. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
fetchCanvasProviderProgram now counts provider_user_mappings from the DB instead of summing total_students from Canvas, so the programs page enrollment figure reflects only users mapped to UnlockEd. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…f enrollment display
Adds a computed field to track whether an event originates from Canvas LMS. This field is not persisted to the database (gorm:"-") and is set at runtime. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Adds fetchCanvasCalendarEvents and appendCanvasEventsForFacility to canvas_programs.go, and merges Canvas events into handleGetAdminCalendar when no class_id filter is applied. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ents - Replace raw context_codes[] string concatenation with url.Values so brackets are percent-encoded in the calendar events URL - Add pagination loop (Link rel="next") for both the courses fetch and the calendar events fetch so all pages are accumulated - Add nextPageURL helper to parse Canvas Link headers - Add comment noting the 1_000_000 per-provider ID range assumption Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… detail sheet - Add is_canvas_event flag to FacilityProgramClassEvent type - Color Canvas events blue in the calendar (blue-700/blue-900) - Show Canvas badge on calendar event tiles via CalendarEventContent component - Add CanvasEventSheet for read-only Canvas event detail display - Route Canvas event clicks to CanvasEventSheet instead of SessionDetailSheet Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… CanvasEventSheet
- Added new route for fetching Canvas class schedule in class_events.go. - Enhanced classes_handler.go to fetch Canvas classes and merge with existing classes. - Implemented fetchCanvasUserPrograms and fetchCanvasCoursesForUser methods in user_handler.go for retrieving user programs from Canvas. - Updated ResidentProgramClassInfo model to include isCanvas field. - Modified ClassesPage to filter and display Canvas classes appropriately. - Added CanvasScheduleTab component for displaying Canvas events in class detail view. - Updated various admin and program overview pages to handle Canvas-specific logic and display. - Enhanced UI to show Canvas badges and adjust layout based on program type.
|
@gtonye expected date of enrollment to be a real date versus 0001-01-01. See pic below:
|
|
@gtonye a part of listed item 5 is a reoccurring issue
2026-06-16.14-42-57.mp4
If you feel that this column will not be needed in the future then we should be able to remove the following migration script along with any related code. 00071_add_enrollment_types_to_provider_platforms.sql
2026-06-16.15-53-07.mp4@gtonye The number within the Enrollment column on the Programs listing page does not match the number of enrollments within the metrics card on the
Also, for facility admins, numbers tend to be off for number of enrollments, please see facility admin video below: 2026-06-16.16-17-15.mp4
|
chore: resolves 1 in UnlockedLabs#1165 (comment)
chore: resolves 2 of UnlockedLabs#1165 (comment)
chore: resolves 4 for UnlockedLabs#1165 (comment)
|
Thank you @carddev81 for taking the time to review in detail. The recent changes should address all the elements in the feedback. 1. A resident already in Linked residents (StudentSteve) is still selectable under 'Link existing' from Needs review — intended?
2. Can't cancel/clear 'Confirm' selections in Needs review — only option is 'Apply # match(es)'.
3. All Canvas user types are showing instead of just students. Looks like the
4. Enrollment counts are inconsistent: Unlink + Link existing keeps counting deleted records (false positives), the Programs listing Enrollment/Classes columns don't match the Statewide Program View metric cards, and facility admin numbers are off (fac admin numbers off could be because of false positives).
5. New Classes page loads slowly with more than 10 facilities (see first video in #4).
|
|
This should be ready for re-review. The recent changes include fixes for:
Note: As discussed offline, there is an inconsistency across Canvas instances where certain API versions appear to handle the |
|
@gtonye I was able to duplicate error page consistently. The error occurs the very first time the cache is loaded from the programs page. I captured the logging output for you as well as the devtools. Check out video below: Here are the steps to reproduce on your machine
2026-06-18.10-03-16.mp4 |
…itial program load
…ronous go routine feat: enhance frontend to support asynchronous loading in programs list and program details pages
|
I'm trying another approach. I suspect the Canvas API is slow, especially on that initial call. Can you test again with the most recent change and let me know if it's still causing the error? Technical NotesWe've moved all program computation inside a goroutine triggered by a cache miss. This is due to Canvas being slow and program computation requiring iteration over all courses to calculate enrollment counts. canvas-integration-async-demo.mov |
carddev81
left a comment
There was a problem hiding this comment.
Looks and works great.














Pre-Submission PR Checklist
Description of the change
This PR introduces full Canvas provider platform integration — surfacing Canvas courses as programs, merging Canvas calendar events into the admin schedule view, and providing facility admins with tooling to link (map) residents to their Canvas accounts.
Canvas Programs via live API lookup
When the Programs overview table is fetched, the backend performs a live lookup against every enabled Canvas provider platform and synthesizes a
ProgramsOverviewTableentry per provider. These entries appear alongside native UnlockEd programs but carrysource: "canvas"and a stable synthetic ID (CanvasProgramIDOffset + providerID). The facility admin program view shows a read-only banner for Canvas programs and suppresses edit controls accordingly.sequenceDiagram participant Client participant API as GET /api/programs participant NATS as NATS KV (canvas_programs) participant Canvas as Canvas API Client->>API: GET /api/programs API->>API: query DB for native programs loop each enabled Canvas provider API->>NATS: get cache key canvas_program_{id} alt cache hit (< 5 min old) NATS-->>API: cached ProgramsOverviewTable else cache miss / stale API->>Canvas: GET /accounts/{id}/courses?per_page=100 Canvas-->>API: course list API->>NATS: put canvas_program_{id} (TTL 5 min) NATS-->>API: ok end end API-->>Client: native programs + Canvas programs mergedResults are persisted in a dedicated
canvas_programsNATS KV bucket with a 5-minute TTL. On a cache miss the handler calls Canvas directly, then writes the result back. Individual program routes (/programs/:id, archive-check, history) are guarded by the sameCanvasProgramIDOffsetsentinel and return safe no-op responses for Canvas IDs, so the frontend does not need special-casing beyond the read-only banner.Canvas User Matching
Facility admins must link residents before Canvas enrollment data appears on resident profiles. Canvas users and UnlockEd residents are matched automatically using a composite name-similarity score (Jaro-Winkler 60% + Smith-Waterman-Gotoh 40%):
Until mappings exist, Canvas enrollment counts reflect 0 mapped users.
Canvas calendar events on admin schedule
Canvas calendar events are fetched (paginated via the
Linkheader) and merged into the admin calendar. Canvas events render in blue (blue-700) and show a "Canvas" badge on the event tile. Clicking a Canvas event opens aCanvasEventSheet(separate from the existingSessionDetailSheetused for native sessions).Other changes
enrollment_typesfield added toProviderPlatform(new migration,[]stringserialized as JSON).AfterFindhook) to avoid auth failures from keys pasted with trailing newlines.ProviderPlatformManagementandProviderPlatformDetailadmin pages wired into sidebar navigation under aProviderAccessfeature flag.Screenshot(s)
Screenshots of the different screens are available in this document.
Additional context
Local setup
To test locally, you will need a running Canvas instance.
You can use the Docker-based one:
Technical notes
100_000_000) to avoid collisions with native program IDs. All routes that accept a program ID check this sentinel to route to Canvas-specific handlers. Reviewers should verify this boundary is consistent across the frontend (isCanvasProgram = id >= 100_000_000) and backend (CanvasProgramIDOffset).Considerations for Future Maintenance
High-Level Architecture and Philosophy
The Provider system uses an abstraction layer that separates provider logic from the main UnlockEd codebase.
This comes with tradeoffs to consider:
This may require drilling into provider APIs and data structures to map them against UnlockEd data models. We have done that mapping work here: https://docs.google.com/spreadsheets/d/15BZiP_TyY-Cg2LqoM22GkLnEK3lU_2UobL64EzukgyM/edit?usp=sharing
Program Concept
Canvas is course-based, so building a program dynamically will always involve latency, as the Canvas API is designed to be consumed per course.
We have implemented parallel calls combined with caching to mitigate this, but a Canvas instance with a large number of courses will still present challenges.
Persisting the data would help, but any persistence strategy needs to account for keeping data in sync with Canvas. The provider code includes logic for background jobs to support this.
Timezone
We have noticed some discrepancies in time handling. While storing times in UTC is generally good practice, we have seen in other projects that storing the timezone alongside the UTC timestamp can help with display reconciliation on the frontend.
We have also observed that frontend code sometimes uses UTC without applying the user's timezone (see Program Overview and the class header).
For Canvas, we propagate the timezone from the course all the way to the frontend to ensure consistent display between Canvas and UnlockEd.
One suggestion would be to display the timezone to the user to avoid confusion — this is especially worthwhile if users spanning multiple timezones is a realistic scenario.