Skip to content

feat: container info sidebar and add sidebar updates#2830

Open
navinkarkera wants to merge 50 commits intoopenedx:masterfrom
open-craft:navin/fal-4293/container-info-sidebar
Open

feat: container info sidebar and add sidebar updates#2830
navinkarkera wants to merge 50 commits intoopenedx:masterfrom
open-craft:navin/fal-4293/container-info-sidebar

Conversation

@navinkarkera
Copy link
Contributor

@navinkarkera navinkarkera commented Jan 20, 2026

Description

screencast.mp4
  • Adds section, subsection and unit sidebar info tab in course outline as described in Course Outline: Section/Subsections/Unit Info Sidebar #2638
  • Updates the sidebar design and behaviour as per Course outline: Add sidebar updates #2826
  • Updates course outline to use react query and removes redux store usage as much as possible. Updated parts that require absolutely cannot work without redux without heavy refactoring (will require quiet some time) to work in tandem with react-query.
  • Due to above change, this PR has a lot of refactor to make sure data is properly synced in outline after any action.

Following items are not implemented:

Apologies for the huge number of changes, could be help it due to migrating parts to react-query

Supporting information

Testing instructions

Other information

Include anything else that will help reviewers and consumers understand the change.

  • Does this change depend on other changes elsewhere?
  • Any special concerns or limitations? For example: deprecations, migrations, security, or accessibility.

Best Practices Checklist

We're trying to move away from some deprecated patterns in this codebase. Please
check if your PR meets these recommendations before asking for a review:

  • Any new files are using TypeScript (.ts, .tsx).
  • Avoid propTypes and defaultProps in any new or modified code.
  • Tests should use the helpers in src/testUtils.tsx (specifically initializeMocks)
  • Do not add new fields to the Redux state/store. Use React Context to share state among multiple components.
  • Use React Query to load data from REST APIs. See any apiHooks.ts in this repo for examples.
  • All new i18n messages in messages.ts files have a description for translators to use.
  • Avoid using ../ in import paths. To import from parent folders, use @src, e.g. import { initializeMocks } from '@src/testUtils'; instead of from '../../../../testUtils'

@openedx-webhooks openedx-webhooks added open-source-contribution PR author is not from Axim or 2U core contributor PR author is a Core Contributor (who may or may not have write access to this repo). labels Jan 20, 2026
@openedx-webhooks
Copy link

openedx-webhooks commented Jan 20, 2026

Thanks for the pull request, @navinkarkera!

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.

@github-project-automation github-project-automation bot moved this to Needs Triage in Contributions Jan 20, 2026
@navinkarkera navinkarkera force-pushed the navin/fal-4293/container-info-sidebar branch from 5f8ac0b to feba274 Compare January 20, 2026 14:53
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Waiting on Author in Contributions Jan 20, 2026
@navinkarkera navinkarkera force-pushed the navin/fal-4293/container-info-sidebar branch 2 times, most recently from 9f4a330 to 4f29a90 Compare January 27, 2026 07:26
@codecov
Copy link

codecov bot commented Jan 27, 2026

Codecov Report

❌ Patch coverage is 95.47511% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.21%. Comparing base (f900ace) to head (77608f7).

Files with missing lines Patch % Lines
src/generic/resizable/Resizable.tsx 63.33% 11 Missing ⚠️
...e-outline/outline-sidebar/LibraryReferenceCard.tsx 94.59% 4 Missing ⚠️
...-outline/outline-sidebar/OutlineSidebarContext.tsx 94.11% 3 Missing ⚠️
src/course-outline/card-header/CardHeader.tsx 92.30% 2 Missing ⚠️
src/course-outline/hooks.jsx 95.00% 2 Missing ⚠️
src/course-outline/outline-sidebar/InfoSidebar.tsx 91.30% 2 Missing ⚠️
...course-outline/outline-sidebar/UnitInfoSidebar.tsx 94.59% 2 Missing ⚠️
src/course-outline/data/apiHooks.ts 97.22% 1 Missing ⚠️
src/course-outline/data/slice.ts 0.00% 1 Missing ⚠️
src/course-outline/outline-sidebar/AddSidebar.tsx 98.73% 1 Missing ⚠️
... and 1 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2830      +/-   ##
==========================================
+ Coverage   95.18%   95.21%   +0.03%     
==========================================
  Files        1319     1326       +7     
  Lines       30022    30355     +333     
  Branches     6748     6861     +113     
==========================================
+ Hits        28575    28902     +327     
- Misses       1378     1384       +6     
  Partials       69       69              

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

@navinkarkera navinkarkera force-pushed the navin/fal-4293/container-info-sidebar branch 4 times, most recently from 3ceae1c to 5590401 Compare February 1, 2026 11:14
@navinkarkera navinkarkera marked this pull request as ready for review February 2, 2026 15:14
@navinkarkera navinkarkera requested a review from rpenido February 2, 2026 15:18
@navinkarkera navinkarkera force-pushed the navin/fal-4293/container-info-sidebar branch from 2cc17aa to e685ab3 Compare February 3, 2026 07:11
Copy link
Contributor

@rpenido rpenido left a comment

Choose a reason for hiding this comment

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

First of all, awesome work here @navinkarkera!
Thanks for all the refactors! After this PR, we will be way closer to saying goodbye to our redux store.

I asked a few clarifying questions about our invalidation strategy.
Everything worked as expected, except for a small bug with the taxonomy button on the header.

iconAs={EditIcon}
onClick={onClickEdit}
onClick={onEditClick}
// @ts-ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this was fixed upstream

Suggested change
// @ts-ignore

Comment on lines 205 to 206
* @param {string} itemId
* @returns {Promise<Object>}
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need this anymore.

Suggested change
* @param {string} itemId
* @returns {Promise<Object>}

await callback?.(data.locator, variables.parentLocator);
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseItemId(variables.parentLocator) });
queryClient.invalidateQueries({
queryKey: courseOutlineQueryKeys.courseDetails(getCourseKey(data.locator)),
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to invalidate the courseDetails here? Do you know which data it sync?

Maybe this is fixing a sync issue by its side effects: refetching the course details would cause a re-render, which will resync the data.

It would be preferable that we find the root cause, or, if it is starting to become complicated, just invalidate the whole course with courseOutlineQueryKeys.course(getCourseKey(data.locator)),

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rpenido We invalidate courseDetails to update the course sync status which is displayed in status bar in course outline. I am not sure what you mean by re-render but this api is quiet small and only used in fetching course metadata (not the whole course outline).

Comment on lines 78 to 79
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseDetails(courseId) });
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseItemId(variables.itemId) });
Copy link
Contributor

Choose a reason for hiding this comment

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

We shouldn't need to invalidate anything besides containerComparisonQueryKeys.course(courseId) here, since they all have the same predicate.

Could you check it?

Suggested change
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseDetails(courseId) });
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseItemId(variables.itemId) });

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, courseOutlineQueryKeys.courseDetails is different from courseOutlineQueryKeys.course and courseOutlineQueryKeys.courseItem. They are related to fetching outline data while course details will just fetch course metadata.

If we invalidate courseOutlineQueryKeys.course(courseId), it will invalidate all course item queries unnecessarily. We only need to invalidate the item itself and the course metadata to update status bar.

Copy link
Contributor

Choose a reason for hiding this comment

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

My bad! I just noted that containerComparisonQueryKeys.course was a different key.

mutationFn: publishCourseItem,
onSettled: async (_data, _err, itemId) => {
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseItemId(itemId) });
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseDetails(getCourseKey(itemId)) });
Copy link
Contributor

Choose a reason for hiding this comment

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

Same thing with the courseDetail here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

/**
* Creates a resizable box that can be dragged to resize the width from the left side.
*/
export const ResizableBox = ({
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!!

Comment on lines +66 to +67
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */}
<div className="resizable-handle" onMouseDown={onMouseDown} />
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be nice to add a button/handler here. I just noticed that the sidebar was resizable after looking at the code.
Also, it should help with accessibility.

This could be discussed upstream and implemented in a follow-up task if needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. However does the resizing really matter in accessibility?

Copy link
Contributor

Choose a reason for hiding this comment

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

Honestly, I have no idea.

Comment on lines +17 to +22
queryClient.invalidateQueries({
queryKey: courseOutlineQueryKeys.courseItemId(contentId),
});
queryClient.invalidateQueries({
queryKey: courseOutlineQueryKeys.courseDetails(courseKey),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as commented above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines 111 to 113
queryClient.invalidateQueries({
queryKey: courseOutlineQueryKeys.courseItemId(currentSelection?.sectionId),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we invalidating a section when opening a unit here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was used to invalidate parent data after a new unit is created but not needed anymore as it is handled in the source.

const showNewSidebar = getConfig().ENABLE_COURSE_OUTLINE_NEW_DESIGN?.toString().toLowerCase() === 'true';
if (showNewSidebar) {
setCurrentPageKey('align', cardId);
setCurrentPageKey('align');
Copy link
Contributor

Choose a reason for hiding this comment

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

Clicking on the taxonomy tag on the card header is crashing here:

TypeError
can't access property "component", pages[currentPageKey] is undefined
Call Stack
 Sidebar
  src/generic/sidebar/Sidebar.tsx:76:28
 renderWithHooks
  node_modules/react-dom/cjs/react-dom.development.js:15486:27
 updateFunctionComponent
  node_modules/react-dom/cjs/react-dom.development.js:19613:20
 beginWork
  node_modules/react-dom/cjs/react-dom.development.js:21636:16
 callCallback
  node_modules/react-dom/cjs/react-dom.development.js:4164:14
 invokeGuardedCallbackDev
  node_modules/react-dom/cjs/react-dom.development.js:4213:16
 invokeGuardedCallback
  node_modules/react-dom/cjs/react-dom.development.js:4277:31
 beginWork$1
  node_modules/react-dom/cjs/react-dom.development.js:27486:28
 performUnitOfWork
  node_modules/react-dom/cjs/react-dom.development.js:26592:12
 workLoopSync
  node_modules/react-dom/cjs/react-dom.development.js:26501:22

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rpenido This was a weird one. My guess is that getConfig doesn't have all values set at the top level (initial render), so it decides that taxonomy is not enabled and so align page is not included in sidebar.

I had to update sidebar page provider to set the pages value inside OutlineSidebarPagesProvider which is component and so getConfig returns correct values and the align page is included. See 112b256

But this change now forces using OutlineSidebarPagesProvider and also the user of this provider has to use pages props to update sidebar pages in plugin.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch!
I don't think that we need this pages props here.
A plugin could override it by nesting another Provider, the way is shown in the comment before the context.
And that way, we can have more control at the plugin to also remove pages, change order, etc..

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch @navinkarkera!
I would prefer that we do not use the pages prop, so we could have more flexibility (removing pages, changing order, etc..) on the plugin end.

I ported your fix (removing the props) here: 10b24e8

@navinkarkera navinkarkera requested a review from rpenido February 4, 2026 10:14
@navinkarkera navinkarkera force-pushed the navin/fal-4293/container-info-sidebar branch from 93f0d3a to 102034e Compare February 4, 2026 12:27
Copy link
Contributor

@rpenido rpenido left a comment

Choose a reason for hiding this comment

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

LGTM 👍
Thank you for your work, @navinkarkera!

  • I tested this using the instructions from the PR
  • I read through the code
  • I checked for accessibility issues
  • Includes documentation

Just left one comment about removing the pages props from the provider.

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

Labels

core contributor PR author is a Core Contributor (who may or may not have write access to this repo). open-source-contribution PR author is not from Axim or 2U

Projects

Status: Waiting on Author

Development

Successfully merging this pull request may close these issues.

3 participants