Skip to content

feat(marketplace): add read-only listing and question preview - #8481

Merged
LWS49 merged 4 commits into
lws49/feat-marketplace-pr1-foundationfrom
lws49/feat-marketplace-pr5-preview
Jul 31, 2026
Merged

feat(marketplace): add read-only listing and question preview#8481
LWS49 merged 4 commits into
lws49/feat-marketplace-pr1-foundationfrom
lws49/feat-marketplace-pr5-preview

Conversation

@LWS49

@LWS49 LWS49 commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

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, ListingPreview and QuestionPreview, are reachable from the marketplace index's Preview action, backed by new controller/jbuilder endpoints that serialize an assessment and its questions cross-instance via ActsAsTenant.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

  • Question detail is a dedicated full-page route laid out like the edit form's sections, rather than an inline expansion, since it's the most faithful layout for heavy content like Programming test cases; the back button returns to the listing preview.
  • All 7 question types get full-depth read-only detail renderers in this PR rather than a partial subset, since the cross-tenant serialization groundwork is shared across types once it exists for one.
  • The listing row date column shows "Published" (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.
  • The assessment properties table shows a curated subset of the real assessment show page and drops the source course's Starts at/Ends at, since showing them would wrongly imply duplication carries those dates over.

Regression prevention

Tests cover: the cross-tenant listing/question show endpoints and their authorization; all 7 question-type detail renderers dispatching from the type discriminator; from_tab propagating 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; and DuplicationAssessmentTree'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.

@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr5-preview branch 2 times, most recently from a31b07a to 85ab1fc Compare July 17, 2026 07:14
@LWS49
LWS49 marked this pull request as ready for review July 17, 2026 07:34
@LWS49 LWS49 changed the title Lws49/feat marketplace pr5 preview feat(marketplace): add read-only listing and question preview Jul 17, 2026
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr4-duplicate branch from d84d375 to 284c2bb Compare July 17, 2026 08:10
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr5-preview branch from 85ab1fc to a7086e3 Compare July 17, 2026 08:11
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr4-duplicate branch from 284c2bb to 0ac76f3 Compare July 29, 2026 10:17
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr5-preview branch from a7086e3 to e04d41c Compare July 29, 2026 10:18
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr4-duplicate branch from 0ac76f3 to 56b15aa Compare July 29, 2026 10:29
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr5-preview branch from e04d41c to 5751e1b Compare July 29, 2026 10:29
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr4-duplicate branch 2 times, most recently from 0b85578 to b218c92 Compare July 30, 2026 03:02
Base automatically changed from lws49/feat-marketplace-pr4-duplicate to lws49/feat-marketplace-pr1-foundation July 30, 2026 03:02
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr5-preview branch 4 times, most recently from ec707c5 to a6b89bd Compare July 30, 2026 05:00
LWS49 added 2 commits July 30, 2026 15:41
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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_tab propagation.
  • 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.

Comment on lines +46 to +49
gradingMode: 'autograded' | 'manual';
baseExp: number | null;
bonusExp: number | null;
showMcqMrqSolution: boolean;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Comment on lines +11 to +15
export const withFromTab = (path: string, fromTab: string | null): string => {
if (!fromTab) return path;
const separator = path.includes('?') ? '&' : '?';
return `${path}${separator}${FROM_TAB_PARAM}=${fromTab}`;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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?

@LWS49 LWS49 Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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).

Comment on lines +3 to +5
// 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed following suggestion.

Comment thread client/locales/zh.json Outdated
Comment on lines +6080 to +6085
"course.marketplace.colPublished": {
"defaultMessage": "发布时间"
},
"course.marketplace.colPublisher": {
"defaultMessage": "发布者"
},

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed.

Comment thread client/locales/ko.json Outdated
Comment on lines +6086 to +6091
"course.marketplace.colPublished": {
"defaultMessage": "게시 일시"
},
"course.marketplace.colPublisher": {
"defaultMessage": "게시자"
},

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed.

LWS49 added 2 commits July 31, 2026 01:17
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.
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr5-preview branch from 2f5f481 to 3d472f2 Compare July 30, 2026 17:18
@LWS49
LWS49 merged commit 64894fc into lws49/feat-marketplace-pr1-foundation Jul 31, 2026
14 checks passed
@LWS49
LWS49 deleted the lws49/feat-marketplace-pr5-preview branch July 31, 2026 04:49
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