Skip to content

feat(marketplace): add data model, migrations, and permissions#8476

Open
LWS49 wants to merge 15 commits into
lws49/feat-ext-assessments-pr4-importfrom
lws49/feat-marketplace-pr1-foundation
Open

feat(marketplace): add data model, migrations, and permissions#8476
LWS49 wants to merge 15 commits into
lws49/feat-ext-assessments-pr4-importfrom
lws49/feat-marketplace-pr1-foundation

Conversation

@LWS49

@LWS49 LWS49 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator
  • add Listing and Adoption models under Course::Assessment::Marketplace namespace, with course_assessment_marketplace_ prefix on both tables
  • Listing tracks published state and publisher, with a uniqueness constraint per assessment and an adoption_count helper
  • Adoption links a listing to a destination course and duplicated assessment, one adoption per duplicated assessment
  • wire has_one :marketplace_listing onto Course::Assessment
  • add AssessmentMarketplaceAbilityComponent: admins can publish listings, course managers/owners can access, duplicate, and preview published listings

LWS49 added 14 commits July 2, 2026 12:00
…links & sorting

Introduce the course gradebook: a frozen-column table of students × assessments
with a column picker, CSV export, and per-grade links into submissions.

Gradebook table
- Page with TanStack-backed table: pinned checkbox + Name columns, sticky
  header and Max Marks rows, frozen-column border seams that survive sticky
  scroll compositing.
- Column picker (assessments grouped by tab/category) and CSV export of the
  selected columns; empty-state hint when no data columns are chosen.
- External ID column, shown when any student has one.
- Grade cells link to the student's submission; a dismissible GradeLinkHint
  banner explains the affordance (persisted per-user via useDismissibleOnce).
- "Search students" global search.
- Default sort with name ascending, null/undefined at bottom of sort regardless of order

Shared table builder
- getColumnCanGlobalFilter is gated on column visibility ("search what you
  see"): hiding a column via the picker removes it from search, and the
  nullable-first-row type sniff is bypassed. Affects all TanStack tables.

Backend
- Gradebook controller, ability and course component; index JSON serializes
  students, assessments, submissions (with submissionId) and gamification.
- Submission grade query also selects the submission id for grade links.

- Remove the redundant ScoreAssessmentSummary download from Statistics.
Add weighted view built on top of gradebook:

- add tables course_gradebook_contributions and course_gradebook_assessment_contributions
- weighted table with equal/custom weight modes and per-assessment
  weight inputs, with a sum gate on custom weights
- points / percentage display toggle
- inline per-student assessment breakdown (row expand)
- projected-total hint
- gradebook_excluded column, serialization, and update-weights API echo
- per-assessment include/exclude in the configure-weights modal,
  seeding custom weights from included assessments only
- excluded assessments shown in the breakdown with no contribution
- add SegmentedSelect component for stylized selection that is not "on-off"
- map student Level to grade-points via a safe parsed arithmetic formula
- fold the Level term into weighted Total and per-student breakdown
- add Configure Contributions controls: formula, level, show, clamp
- persist a singleton LevelConfig per course (new table + migration)
- surface a Level column in the weighted table, toggleable via column picker
- warn when a contribution falls outside the range of level contributions
- add toggle to clamp contributions within the range of level contributions
Expanding a student in the weighted gradebook now keeps that student's
summary row pinned beneath the sticky header while their per-assessment
breakdown is open, so the totals stay visible while scrolling the detail.

The view becomes a single-open accordion (opening one student collapses
any other), the focused row auto-scrolls to just under the header on
expand (respecting prefers-reduced-motion), and focus stays on the
expand/collapse toggle for keyboard users.
- Permit keepHighest in update_weights_params
- Parse keepHighest from request and pass to bulk_update via keep_highest
- Echo keepHighest in serialize_weight_updates response
- Serialize keepHighest from contribution in index.json.jbuilder
- Add controller tests: round-trip persist and index response
- Add model tests: bulk_update persists keep_highest in equal mode
…guard

Render level contribution in percent mode, and enforce that an enabled level
contribution always has a parseable formula:

- Model: validate on :formula (not the hidden :formula_ast) that an enabled
  config with a formula carries a non-blank ast — the error surfaces on the
  field the user edits, and an enabled config can no longer silently evaluate
  to nothing.
- Dialog: parse the formula whenever level is enabled, so clearing it blocks
  Save and flags the field instead of round-tripping to a confusing 422.
- Controller: wrap the tab-weight and level-config writes in one transaction
  so a rejected formula rolls back the tab weights instead of a partial save.
…'t add up +show honest weighted total in percent mode
…ation, inline grade entry)

Foundation layer for external assessments: data model + migrations, gradebook read serialization, and inline grade entry (update_grade). Management UI (panel, add/edit/delete), weight integration, and CSV import are introduced in later PRs of the stack.
…ete, reorder, weight integration

Adds the management surface on top of the read-only foundation:

- Manage panel (launched from the gradebook + weighted toolbars) with add,
  edit, and delete prompts; drag-and-drop reordering of external columns.
- Weighted-view integration: external rows in Configure Weights (name + single
  weight, no mode/expand), and flat external grouping in the column picker.
- Store reducers + thunks for create/update/delete/reorder; API + types.
- Backend: create/update/destroy/reorder actions with jbuilders; a per-course
  `position` column (append-on-create, reorder! rewrites order); routes.
…warnings

External grade cells gain decimal-bounded inline editing with explicit
accept/revert controls, a saving indicator, and a persistent save
confirmation that echoes the student, assessment, and old → new grade so a
row/column misclick is caught. Over-max and below-zero warning markers flag
out-of-bound values. The weighted view annotates contributions that an
active bound capped or floored. GradebookIndex surfaces an out-of-range
summary alert above the table, and threads weightedViewEnabled into
GradebookTable so the cell messaging matches the active view.

m
Introduce the full external-assessment CSV import feature as a single
slice on top of grade validation (pr3).

Backend:
- Course::Gradebook::ExternalAssessmentImportService — header-order
  tolerance, duplicate-header/identifier guards, reassigned-identifier
  detection, out-of-range flagging, and batched (bulk insert + upsert)
  grade writes.
- external_assessment_imports controller (preview + create/commit),
  create/preview jbuilders, and import routes.

Frontend:
- ImportExternalAssessmentsWizard (upload → define → verify), with the
  ExternalGradeConflict prompt/table change-matrix and buildTemplate.
- previewImport/commitImport operations + import API endpoints + types.
- ManageExternalAssessmentsPanel gains the Import CSV entry point.
- levelFormula: make negative rounding consistent with ruby, fix error message when more than 2 arguments are used for min and max
- WeightedGradebookTable: add cell tooltip for capping total at 100%
- ConfigureWeightsPrompt: change capTotal to no longer be gated to when total weightage adds to over 100%
- ImportWizard: successful import toast now shows how many grades got replaced/kept if import resolved from conflictPrompt
- update to locales based on the above changes
…ort UI

- unify manage and configure-weights into a GradebookSettingsDialog
- decompose import wizard into ColumnMappingTable + useImportMapping hook
- extract importValidation module with dedicated unit tests
- rework ExternalAssessmentImportService header/identifier handling
- refresh en/ko/zh locales for the new settings surface
@LWS49 LWS49 force-pushed the lws49/feat-marketplace-pr1-foundation branch 2 times, most recently from 35884a1 to 833d37f Compare July 8, 2026 03:58
- add Listing and Adoption models under Course::Assessment::Marketplace
  namespace, with course_assessment_marketplace_ prefix on both tables
- Listing tracks published state and publisher, with a uniqueness
  constraint per assessment and an adoption_count helper
- Adoption links a listing to a destination course and duplicated
  assessment, one adoption per duplicated assessment
- wire has_one :marketplace_listing onto Course::Assessment
- add AssessmentMarketplaceAbilityComponent: admins can publish listings,
  course managers/owners can access, duplicate, and preview published
  listings
@LWS49 LWS49 force-pushed the lws49/feat-marketplace-pr1-foundation branch from 833d37f to 9926c90 Compare July 8, 2026 04:05
@LWS49 LWS49 force-pushed the lws49/feat-ext-assessments-pr4-import branch 2 times, most recently from d70b9ff to b7da646 Compare July 9, 2026 14:37
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