feat(marketplace): add read-only listing and question preview - #8481
Conversation
a31b07a to
85ab1fc
Compare
d84d375 to
284c2bb
Compare
85ab1fc to
a7086e3
Compare
284c2bb to
0ac76f3
Compare
a7086e3 to
e04d41c
Compare
0ac76f3 to
56b15aa
Compare
e04d41c to
5751e1b
Compare
0b85578 to
b218c92
Compare
ec707c5 to
a6b89bd
Compare
Add the read-only backend for the marketplace browse flow: - listings#show serializes a curated, read-only view of a published assessment (config + per-question summaries) for the listing preview. - questions#show serializes a single question's detail, dispatching to per-type detail partials (multiple/text/voice/forum/programming/ rubric/scribing) so each renderer gets exactly the data it needs. - The listings index gains destination tabs plus preview/duplicate URLs so the browse table can link into the flow and target a tab. Type labels are serialized human-readable (question_type_readable) to match the real assessment show page, while the demodulized discriminator is kept for frontend renderer dispatch. The base controller pulls in AssessmentsHelper so the preview views can reuse display_graded_test_types, and the sidebar component now uses the :marketplace (storefront) icon.
…port Extract the assessment/tab/question tree from AssessmentsListing into a reusable DuplicationAssessmentTree component so both the duplication page and the marketplace duplicate dialog render an identical tree. The old DuplicateItemsConfirmation listing is rewired onto it. Also add the shared table primitives the marketplace index needs: - renderEmpty flows through TableTemplate -> Body -> MuiTable so a table can render a custom empty state when it has no rows. - hideSelectAll drops the select-all header checkbox while keeping the per-row checkboxes. - Register the storefront icon in COURSE_COMPONENT_ICONS.
a6b89bd to
2f5f481
Compare
There was a problem hiding this comment.
Pull request overview
Adds a read-only marketplace preview flow for published assessment listings, including per-question full-page previews across instances, and improves the marketplace browse + duplication UX (routing, breadcrumbs, table UX, and confirmation dialog).
Changes:
- Adds marketplace listing + question preview endpoints (Rails controllers + Jbuilder) that read cross-tenant via
ActsAsTenant.without_tenant. - Adds new React routes/pages/renderers for listing + question previews (all 7 question types), plus breadcrumb handles and
from_tabpropagation. - Refactors marketplace index table UX (published date column, pagination, empty state, toolbar/selection tweaks) and reworks duplication confirmation to show destination course + assessment tree.
Reviewed changes
Copilot reviewed 67 out of 67 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/controllers/course/assessment/marketplace/questions_controller_spec.rb | Adds controller specs for cross-instance question preview serialization + authorization. |
| spec/controllers/course/assessment/marketplace/listings_controller_spec.rb | Extends marketplace listing specs for destination tabs and preview show endpoint behavior. |
| spec/controllers/course/assessment_marketplace_component_spec.rb | Verifies marketplace sidebar item icon. |
| config/routes.rb | Adds nested marketplace question preview route under listings. |
| client/locales/zh.json | Updates marketplace UI strings (Chinese). |
| client/locales/ko.json | Updates marketplace UI strings (Korean). |
| client/locales/en.json | Updates marketplace UI strings (English). |
| client/app/routers/course/marketplace.tsx | Adds nested routes for listing/question preview pages and route handles. |
| client/app/lib/components/table/TanStackTableBuilder/useTanStackTableBuilder.tsx | Threads hideSelectAll and renderEmpty into table builder. |
| client/app/lib/components/table/TanStackTableBuilder/columnsBuilder.ts | Adds option to hide select-all checkbox in header while keeping row checkboxes. |
| client/app/lib/components/table/MuiTableAdapter/MuiTable.tsx | Adds rendering for a custom empty state when there are no rows. |
| client/app/lib/components/table/builder/TableTemplate.ts | Adds renderEmpty to the table template API. |
| client/app/lib/components/table/builder/featureTemplates.ts | Adds hideSelectAll option to indexing template. |
| client/app/lib/components/table/adapters/Body.ts | Allows passing renderEmpty through body props. |
| client/app/bundles/course/marketplace/types.ts | Adds marketplace index/preview data types for listing/question preview. |
| client/app/bundles/course/marketplace/translations.ts | Adds/updates marketplace messages for new UI and preview flow. |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/VoiceResponse.tsx | Adds voice question renderer (no extra section). |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/types.ts | Adds shared renderer prop types. |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/TextResponse.tsx | Adds text response preview renderer (attachments/solutions/comprehension). |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/Scribing.tsx | Adds scribing preview renderer with image/empty-state message. |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/RubricBasedResponse.tsx | Adds rubric preview renderer (categories/criteria + bonus marker). |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/Programming.tsx | Adds programming preview renderer (templates + test-case buckets). |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/MultipleResponse.tsx | Adds MCQ/MRQ preview renderer (answer key + explanations). |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/ForumPostResponse.tsx | Adds forum post response preview renderer (requirements). |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/test/VoiceResponse.test.tsx | Tests voice renderer contributes no content. |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/test/TextResponse.test.tsx | Tests text response renderer output/sections. |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/test/Scribing.test.tsx | Tests scribing renderer image vs empty state. |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/test/RubricBasedResponse.test.tsx | Tests rubric renderer categories + bonus chip. |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/test/Programming.test.tsx | Tests programming renderer templates + test cases. |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/test/MultipleResponse.test.tsx | Tests MCQ/MRQ renderer choice rendering + selection type. |
| client/app/bundles/course/marketplace/pages/QuestionPreview/renderers/test/ForumPostResponse.test.tsx | Tests forum-post requirements rendering. |
| client/app/bundles/course/marketplace/pages/QuestionPreview/index.tsx | Adds question preview page shell + renderer dispatcher. |
| client/app/bundles/course/marketplace/pages/QuestionPreview/test/index.test.tsx | Tests question preview page dispatches by discriminator and renders shell. |
| client/app/bundles/course/marketplace/pages/MarketplaceIndex/MarketplaceTable.tsx | Updates marketplace table UI (published date, icons, selection, empty state, pagination, from_tab). |
| client/app/bundles/course/marketplace/pages/MarketplaceIndex/index.tsx | Threads from_tab, destination tab resolution, and passes destination metadata to confirmation. |
| client/app/bundles/course/marketplace/pages/MarketplaceIndex/test/MarketplaceTable.test.tsx | Adds tests for new table UX behaviors (selection, empty state, pagination, sorting). |
| client/app/bundles/course/marketplace/pages/MarketplaceIndex/test/index.test.tsx | Adds tests for from_tab propagation + destination tab resolution in confirmation. |
| client/app/bundles/course/marketplace/pages/ListingPreview/PreviewQuestionCard.tsx | Adds listing preview question cards (collapsible options + per-question detail link). |
| client/app/bundles/course/marketplace/pages/ListingPreview/PreviewAssessmentDetails.tsx | Adds read-only assessment properties table for listing preview. |
| client/app/bundles/course/marketplace/pages/ListingPreview/index.tsx | Adds listing preview page with back navigation + duplicate action. |
| client/app/bundles/course/marketplace/pages/ListingPreview/test/index.test.tsx | Tests listing preview rendering + from_tab propagation + back navigation. |
| client/app/bundles/course/marketplace/operations.ts | Adds API operations for listing/question fetch; extends index data shape. |
| client/app/bundles/course/marketplace/handles.ts | Adds breadcrumb handles for marketplace, listing, and question preview routes. |
| client/app/bundles/course/marketplace/fromTab.ts | Adds helpers for reading/appending from_tab. |
| client/app/bundles/course/marketplace/components/PublishToMarketplaceButton.tsx | Switches to useTranslation hook for marketplace publish prompt. |
| client/app/bundles/course/marketplace/components/DuplicateConfirmation.tsx | Reworks confirmation dialog to show destination course + assessment tree. |
| client/app/bundles/course/marketplace/components/test/DuplicationConfirmation.test.tsx | Tests new confirmation UI structure and behavior. |
| client/app/bundles/course/marketplace/test/handles.test.ts | Tests breadcrumb handle URL/title behavior with from_tab. |
| client/app/bundles/course/marketplace/test/fromTab.test.ts | Tests from_tab helper functions. |
| client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/AssessmentsListing.tsx | Refactors duplication confirmation listing to use shared assessment-tree component. |
| client/app/bundles/course/duplication/components/DuplicationAssessmentTree.tsx | Introduces reusable category/tab/assessment tree renderer. |
| client/app/bundles/course/duplication/components/test/DuplicationAssessmentTree.test.tsx | Tests shared duplication assessment tree rendering. |
| client/app/api/course/Marketplace.ts | Extends marketplace API client to fetch listing/question previews and destination tabs. |
| app/views/course/assessment/marketplace/questions/show.json.jbuilder | Adds question preview JSON with discriminator + type-specific detail partials. |
| app/views/course/assessment/marketplace/questions/details/_voice_response.json.jbuilder | Adds voice response detail serializer (empty object). |
| app/views/course/assessment/marketplace/questions/details/_text_response.json.jbuilder | Adds text response detail serializer (solutions + attachment settings). |
| app/views/course/assessment/marketplace/questions/details/_scribing.json.jbuilder | Adds scribing detail serializer (imageUrl). |
| app/views/course/assessment/marketplace/questions/details/_rubric_based_response.json.jbuilder | Adds rubric detail serializer (categories/criteria). |
| app/views/course/assessment/marketplace/questions/details/_programming.json.jbuilder | Adds programming detail serializer (templates + test cases). |
| app/views/course/assessment/marketplace/questions/details/_multiple_response.json.jbuilder | Adds multiple response detail serializer (answer key + explanations). |
| app/views/course/assessment/marketplace/questions/details/_forum_post_response.json.jbuilder | Adds forum-post detail serializer (requirements flags). |
| app/views/course/assessment/marketplace/listings/show.json.jbuilder | Adds listing preview JSON serializer (assessment config + question summaries). |
| app/views/course/assessment/marketplace/listings/index.json.jbuilder | Adds destination tabs payload to index response. |
| app/controllers/course/statistics/aggregate_controller.rb | Minor Ruby hash construction refactor. |
| app/controllers/course/assessment/marketplace/questions_controller.rb | Adds question preview controller action with cross-tenant reads + authorization. |
| app/controllers/course/assessment/marketplace/listings_controller.rb | Adds listing preview controller action; refactors index computations; adds destination tabs. |
| app/controllers/course/assessment/marketplace/controller.rb | Includes AssessmentsHelper for preview serialization reuse. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| gradingMode: 'autograded' | 'manual'; | ||
| baseExp: number | null; | ||
| bonusExp: number | null; | ||
| showMcqMrqSolution: boolean; |
There was a problem hiding this comment.
Fixed following suggestion. The endpoint omits these keys rather than sending null (show.json.jbuilder only emits them when > 0), so the type is now baseExp?: number / bonusExp?: number.
| export const withFromTab = (path: string, fromTab: string | null): string => { | ||
| if (!fromTab) return path; | ||
| const separator = path.includes('?') ? '&' : '?'; | ||
| return `${path}${separator}${FROM_TAB_PARAM}=${fromTab}`; | ||
| }; |
There was a problem hiding this comment.
In our current data schema, tab id is always numeric, so this is a minor concern.
However this does mean that we may be able to cast fromTab as a Number?
There was a problem hiding this comment.
Yes we can, and I have modified accordingly. readFromTab now parses at the URL boundary and returns number | null, and withFromTab takes number | null. The encoding concern thus falls out as a side effect, and I have also removed the parseInt that MarketplaceIndex and ListingPreview were each doing to derive destinationTabId since that is not necessary.
Changes made in fromTab.ts, MarketplaceIndex/index.tsx, ListingPreview/index.tsx, MarketplaceTable.tsx, PreviewQuestionCard.tsx, and MarketplaceTable.test.tsx (to update fromTab from string | null to number | null).
| // Voice questions carry no type-specific setup — the prompt is the base description and the max | ||
| // grade are already rendered by the shell's "Question details" and "Grading" sections. Mirroring the | ||
| // native edit UI (which shows nothing extra for voice), this renderer contributes no section. |
There was a problem hiding this comment.
Fixed following suggestion.
| "course.marketplace.colPublished": { | ||
| "defaultMessage": "发布时间" | ||
| }, | ||
| "course.marketplace.colPublisher": { | ||
| "defaultMessage": "发布者" | ||
| }, |
| "course.marketplace.colPublished": { | ||
| "defaultMessage": "게시 일시" | ||
| }, | ||
| "course.marketplace.colPublisher": { | ||
| "defaultMessage": "게시자" | ||
| }, |
Build the read-only browse experience on top of the preview endpoints: - Marketplace index: single-toolbar table with pagination, empty states, hidden select-all, and links into the listing preview / duplicate flow. - Listing preview page: read-only assessment config, per-question cards (type chip, staff-only notes, expandable options) and a Duplicate Assessment action. - Question detail preview: header chip plus a renderer dispatcher with a renderer per question type (multiple/text/voice/forum/programming/ rubric/scribing). - Duplicate dialog now shows the destination course and the shared assessment tree. Includes the api client, operations, types, translations and locale strings backing the above.
Thread the origin assessment tab (from_tab) through the whole browse flow (index -> listing -> question preview and back) via withFromTab helpers, so a duplication always imports into the tab the user started from no matter how they navigate. Add the route data handles that build the marketplace / listing / question breadcrumbs, preserving from_tab on the crumb links.
2f5f481 to
3d472f2
Compare
64894fc
into
lws49/feat-marketplace-pr1-foundation
Summary
This adds a read-only preview flow for marketplace listings, so course staff can inspect a published assessment (and every question in it) before deciding to duplicate it into their own course. Two new pages,
ListingPreviewandQuestionPreview, are reachable from the marketplace index's Preview action, backed by new controller/jbuilder endpoints that serialize an assessment and its questions cross-instance viaActsAsTenant.without_tenant. Along the way the duplicate confirmation dialog is reworked to show the destination course and an assessment tree, the old preview link (which opened a new tab and lost the router) now navigates in-app, and the browse flow's origin tab (from_tab) is threaded through breadcrumbs and every intra-marketplace link so duplication lands back where the user started.Design decisions
first_published_at) rather than "Updated": the publish button is a publish/remove toggle with no in-place re-publish, so a last-updated timestamp would be actively misleading.Regression prevention
Tests cover: the cross-tenant listing/question show endpoints and their authorization; all 7 question-type detail renderers dispatching from the
typediscriminator;from_tabpropagating through breadcrumbs, preview links, and the duplicate confirmation; in-app preview navigation (no new tab) with back-button return; the MarketplaceTable's new Published column and Newest sort; empty-state and pagination behaviour; andDuplicationAssessmentTree's shared rendering (now used by both the marketplace and the generic duplication flow it was extracted from).Manual testing covered the preview flow end-to-end: marketplace index → listing preview → each question type's detail page, the duplicate confirmation dialog, and in-app back navigation.