Skip to content

fix: course optimizer block links#2946

Merged
bradenmacdonald merged 10 commits intoopenedx:masterfrom
mitodl:areeb/course-optimizer-url
Apr 17, 2026
Merged

fix: course optimizer block links#2946
bradenmacdonald merged 10 commits intoopenedx:masterfrom
mitodl:areeb/course-optimizer-url

Conversation

@asajjad2
Copy link
Copy Markdown
Contributor

@asajjad2 asajjad2 commented Mar 16, 2026

What are the relevant tickets?

#10130

Description

The “Go to block” links in the scan results were built from backend URLs that assume the legacy /editor/html/{blockId} pattern, which works on edX but fails on MITx Online where authoring uses /authoring/course/{courseId}/container/{verticalId}#{blockId}. To make links reliable across deployments, the frontend now derives block URLs itself via buildBlockContainerUrl(courseId, unitId, blockId) using the container route and PUBLIC_PATH, and ScanResults passes courseId through to BrokenLinkTable so all broken/locked/manual and previous-run links open the correct unit/block.

Testing Steps

  1. Start Studio + LMS and the authoring MFE.
  2. Log into Studio and open a course’s Course Optimizer page (e.g. .../authoring/course//optimizer).

Run a scan and verify links

  1. Click Scan course and wait for results to appear.
  2. In the Broken links section, click the “Go to block” link for a broken/locked/manual link.
  3. Confirm the browser navigates to a URL of the form /authoring/course//container/# and opens the correct unit with the expected component selected.

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Mar 16, 2026
@openedx-webhooks
Copy link
Copy Markdown

openedx-webhooks commented Mar 16, 2026

Thanks for the pull request, @asajjad2!

This repository is currently maintained by @bradenmacdonald.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.46%. Comparing base (b2a4b9c) to head (f00de05).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff            @@
##           master    #2946    +/-   ##
========================================
  Coverage   95.46%   95.46%            
========================================
  Files        1378     1378            
  Lines       32596    32600     +4     
  Branches     7479     7256   -223     
========================================
+ Hits        31117    31121     +4     
- Misses       1410     1423    +13     
+ Partials       69       56    -13     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@asajjad2 asajjad2 marked this pull request as ready for review March 16, 2026 15:57
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Ready for Review in Contributions Mar 23, 2026
@marslanabdulrauf marslanabdulrauf force-pushed the areeb/course-optimizer-url branch 2 times, most recently from cce5624 to d68f2a6 Compare April 2, 2026 13:00
@bradenmacdonald
Copy link
Copy Markdown
Contributor

Let me know if you want a review here, once the patch coverage is fixed.

@marslanabdulrauf marslanabdulrauf force-pushed the areeb/course-optimizer-url branch from c1c0cd1 to 632f5e1 Compare April 7, 2026 14:44
@marslanabdulrauf
Copy link
Copy Markdown
Contributor

Hi @bradenmacdonald could you please take a look now? thanks.


const BrokenLinkTable: FC<BrokenLinkTableProps> = ({
unit,
courseId,
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.

You can just use

const { courseId } = useCourseAuthoringContext();

within this component; you don't need to accept courseId as a prop.

Comment thread src/optimizer-page/utils.ts Outdated
): string => {
const path = `/course/${courseId}/container/${unitId}#${blockId}`;
const internalPath = createCorrectInternalRoute(path);
return typeof window !== 'undefined'
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.

When is window ever undefined? Could you please add an explanation here?

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.

Actually, instead of this logic, could we just use getConfig().COURSE_AUTHORING_MICROFRONTEND_URL ?

I think process.env.BASE_URL is also supposed to be the same thing, but it doesn't seem to be correct. Likewise, getConfig().PUBLIC_PATH seems correct, but getConfig().BASE_URL is just the domain, not a URL.

@arbrandes Is there a better pattern we should be using for normalizing MFE absolute URLs (pre frontend-base)?

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.

Right now the source of truth is, for better or worse, COURSE_AUTHORING_MICROFRONTEND_URL... in edx-platform.

In theory, in the frontend the ideal thing to do is to use relative links as much as possible, and when unavoidable, to build the URL from window.location. In other words, if we can avoid configuration, we should. The alternative has (again, theoretical) dowsides: getConfig means duplicating the source of truth (unless you're using Tutor's Jinja templates), and waiting for an API call to return base configuration is bad for UX (though in pratice a very minor concern given the bazillion calls we already make).

In practice, we have to deal with this stuff:

export const createCorrectInternalRoute = (checkPath: string): string => {
  let basePath = getPath(getConfig().PUBLIC_PATH);

So we already have to make a getConfig() call anyway, even to build internal relative URLs. So the point about duplicating source of truth is moot. And if it's moot, we might as well reach for the explicit thing we're after.

That thing is supposed to be BASE_URL + PUBLIC_PATH. But unfortunately - and this highlights one of the downsides of configuration - BASE_URL has been (ab|mis)used in the ecossystem, so you can't rely on it.

We could create a new config variable as you suggest, but as we just saw, configuration carries risks. We also don't want to make a new dedicated API call just for this. So we're back, full circle, to the theory: can we do this without configuration - or, at least, with minimal configuration?

IMHO, if we can do it only depending on PUBLIC_PATH, then that feels like the better option.

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.

Thanks for the info. So if we're already using getConfig() to get PUBLIC_PATH, then why don't we just use getConfig().COURSE_AUTHORING_MICROFRONTEND_URL ? I'm not suggesting a new config variable; that one is already there and seems to be correct.

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.

that one is already there

I think you mean this.

So, that's a great example of a "tutor-ism". The only reason that variable is accessible to this MFE is that Tutor bunches them all together into the runtime config. In this case, it's supposed to be useful to frontend-app-admin-console. The same goes for the other XYZ_MICROFRONTEND_URLs: they're meant for other MFEs, not for the MFE itself.

So, yes, the variable is there... but only if you use Tutor and its flavor of runtime config.

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.

What would be the standard way of implementing this? I see just these options:

  1. Expose COURSE_AUTHORING_MICROFRONTEND_URL as a build-time url, which would require us to re-build the image every time this config changes.
  2. Serve different configs at runtime depending on which frontend is requesting it, but I don't know how to do that.

Am I missing something?

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.

Expose COURSE_AUTHORING_MICROFRONTEND_URL as a build-time url, which would require us to re-build the image every time this config changes.

That would be the most standard way. But see my recommendation below.

Serve different configs at runtime depending on which frontend is requesting it, but I don't know how to do that.

I thought this was already happening? The Authoring MFE requests http://localhost:8000/api/mfe_config/v1?mfe=authoring and I thought that the ?mfe=authoring part was responsible for changing the returned config values like BASE_URL. Although now that I check, it seems to be having no effect.


Personally I would recommend moving to a much simpler approach than the current partially build-time/partially run-time method.

The unusual but highly effective approach I have used before (very successfully):

  1. Set all the environment variables like BASE_URL to unique dummy values during the build
  2. In your Dockerfile (example), or before deploying to S3, or whatever deployment step you use, run a script to find/replace the dummy values in the dist/built frontend with the "real" values
  3. Now you get start-time/deploy-time config that is easily updated anytime, and you can use the same MFE base image for many different deployments. No need to muck around with runtime config.

I'm sure I have suggested this before, though...

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.

@bradenmacdonald

it seems to be having no effect

This would only work if the config variables were added to MFE_CONFIG_OVERRIDES[<mfe>][<var>] which is not what's happening Tutor-side.

As for the just-in-time replacement, that is a neat idea (gonna make a note of it for the future), but the artifact that Tutor doesn't want users to have to rebuild (if possible) is the Docker image itself.

@regisb

Serve different configs at runtime depending on which frontend is requesting it

I don't think we need to go to these lengths, at this point. Particularly since the concept of separate MFEs is going away soon (the first step towards that is already being reviewed in tutor-mfe).

Also, I didn't mean to say that Tutor is doing anything wrong. I actually think it does a good job of covering the relative mess that MFEs are (have always been) in with a layer of sanity, for operators. It's just that right now I'm wearing my Frontend Maintainer Hat, pointing out that we shouldn't rely on something Tutor does downstream.

Copy link
Copy Markdown
Contributor

@bradenmacdonald bradenmacdonald Apr 16, 2026

Choose a reason for hiding this comment

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

As for the just-in-time replacement, that is a neat idea (gonna make a note of it for the future), but the artifact that Tutor doesn't want users to have to rebuild (if possible) is the Docker image itself.

That's exactly how I used the just-in-time replacement: by setting the replacement script to run as the "start command" for the image, it lets you re-use a single docker image with different configuration options without rebuilding.

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.

Ah, I missed that "nuance", lol! Well, that's certainly food for thought: maybe we can deprecate runtime config entirely!

@marslanabdulrauf marslanabdulrauf force-pushed the areeb/course-optimizer-url branch from 632f5e1 to 538a6ba Compare April 15, 2026 13:08
@bradenmacdonald
Copy link
Copy Markdown
Contributor

Let me know when you want a final review + merge.

@marslanabdulrauf
Copy link
Copy Markdown
Contributor

yes @bradenmacdonald you can take another look.

Comment thread src/optimizer-page/utils.ts Outdated
@bradenmacdonald
Copy link
Copy Markdown
Contributor

I tested this fix and it's working well, but I think the other fix that I've suggested is better.

@marslanabdulrauf
Copy link
Copy Markdown
Contributor

Alright, it is ready for final review @bradenmacdonald

@bradenmacdonald bradenmacdonald merged commit 4a12a31 into openedx:master Apr 17, 2026
7 checks passed
@github-project-automation github-project-automation Bot moved this from Ready for Review to Done in Contributions Apr 17, 2026
@bradenmacdonald
Copy link
Copy Markdown
Contributor

Thanks for the fix @marslanabdulrauf!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

open-source-contribution PR author is not from Axim or 2U

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants