Skip to content

External routes ignore params #349

Open

Description

@brian-smith-tcril

External routes ignore params. resolveRouteByRole(role, { courseId }) uses courseId when an installed app provides the role, and ignores it when an external route does.

Found while porting Learning's progress tab (openedx/frontend-app-learning#2190), which links to course pages by role. Related to #348.

Note

The details below were generated by Claude (Claude Code) and reviewed before posting.

Claude Plan

How a role with params resolves today

  • A role is declared on a route, and the route's path decides which params the role takes: /gradebook/:courseId carries org.openedx.frontend.role.gradebook (src/routes.tsx in openedx/frontend-app-gradebook), instructor-dashboard/:courseId carries org.openedx.frontend.role.instructorDashboard (src/routes.tsx in openedx/frontend-app-instructor-dashboard).
  • A caller passes those params: resolveRouteByRole(gradebookRole, { courseId }) in the instructor dashboard, resolveRouteByRole(instructorDashboardRole, { courseId }) in gradebook.
  • resolveRouteByRole returns isInternal ? generatePath(path, params) : path. A path in this site has its params filled; for anything else the params are ignored, with no warning, and the URL comes back unchanged. The TSDoc says so ("an external URL is returned as configured"), and a test pins it by passing { courseId: 'c1' } for a role whose external URL is https://apps.example.com/profile/ and expecting that URL back.
  • ExternalRoute is a role and a URL. An external route written as https://lms.example.com/courses/:courseId/progress comes back with the literal :courseId in it.
  • The caller cannot tell which case it is in before it calls: who provides a role is a property of the site, not of the role.
  • One exception follows from the same line: an external route written as a path (a single leading /) is classed as internal by isInternalUrl, so its params are filled, and it is linked through react-router.

What the docs say

  • The migration guide's URL Config changes presents the two as interchangeable: URLs "should now be expressed as part of the apps config as route roles, or as externalRoutes, and resolved in code via resolveRouteByRole(). It substitutes route params, drops a trailing splat, and reports whether the result is a path in this site or an external URL". It does not say the substitution applies to one of the two only.
  • No ADR in docs/decisions/ covers route roles with params or external routes; 0013 mentions roles for the course bar's provides.

How it got here

Where it bites

  • Both call sites above keep a URL of their own for when the role resolves to nothing (gradebookRoute?.url ?? data.gradebookUrl, dashboardRoute?.url ?? instructorDashboardUrl(courseId)), and that fallback is the only way either reaches a destination outside the site with the course in it. A site that declares an external route for one of those roles replaces the fallback with a URL that cannot name the course.
  • The shell's own ProfileLinkMenuItem reaches a user's profile by appending /u/<username> to the profile role's URL, which works for an app route and an external route alike because the role's route takes no param: openedx/frontend-app-profile declares the role on profile and serves the user's page from its child u/:username (src/routes.tsx).
  • Learning declares org.openedx.frontend.role.courseOutline, courseDates, courseProgress and courseware on routes under course/:courseId, and its progress tab links to them by role (Port the progress tab to frontend-base frontend-app-learning#2190). If A site cannot override an installed app's route for a role with an external route #348 lets a site point one of them at an external route, the link the site gets has no course in it.

Questions

  • Should a site be able to provide a role whose route takes params with an external route?
  • If so, how does an external route say where the params go?
  • Until then, where does an app author declaring a role, or an operator writing externalRoutes, learn that params are dropped for an external URL?
  • Should the resolver report it when it is handed params it will not use?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions