-
Notifications
You must be signed in to change notification settings - Fork 211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add plugin slots for progress page components #1496
base: master
Are you sure you want to change the base?
feat: Add plugin slots for progress page components #1496
Conversation
Thanks for the pull request, @xitij2000! What's next?Please work through the following steps to get your changes ready for engineering review: 🔘 Get product approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo 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:
🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. 🔘 Let us know that your PR is ready for review:Who will review my changes?This repository is currently maintained by 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:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
This PR currently doesn't include any screenshots. I will add them before the PR is final depending on the direction of the review. |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #1496 +/- ##
==========================================
+ Coverage 89.81% 89.87% +0.05%
==========================================
Files 325 331 +6
Lines 5598 5631 +33
Branches 1396 1357 -39
==========================================
+ Hits 5028 5061 +33
- Misses 554 555 +1
+ Partials 16 15 -1 ☔ View full report in Codecov by Sentry. |
@xitij2000 I marked this PR as ready for review assuming that it doesn't change any existing user-facing behavior. Let me know if that's wrong, please (it would need to go through product review in that case). CC @openedx/committers-frontend-app-learning |
@xitij2000 This makes sense to me at a high level. Would you mind adding some screenshots, as you suggested? @arbrandes Do we have any specific guidelines around where we'll accept slots in the UI, or just use my best judgement? |
Additionally, if we do want component-level slots (which I think we do since there is a slot to add contents to a unit title), then perhaps we can structure them in some way in he plugin-slots folder? Otherwise, as the number of slots grows it will become unwieldy. |
@xitij2000 @bradenmacdonald I'm a little unclear on the current status of this PR. Are there any blockers to starting engineering review? |
@itsjeyd I don't think so. I just hoping to hear from @arbrandes. |
ace68ba
to
0c462fa
Compare
@bradenmacdonald OK, got it. |
@bradenmacdonald Do you want to explicitly request a review from him, maybe? That might help with getting this PR unstuck. CC @xitij2000 |
@brian-smith-tcril Could I get your thoughts on this PR? I think it sounds like a good idea, and Adolfo suggested we might want to backport it to Sumac, which I agree with. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you so much for this PR!
Overall I think these are great extension points to have, and I'm super happy to see these documented with screenshots and everything!
I left a few comments with questions and suggestions. Most are quite small, but there's one larger comment that might spark conversation.
pluginProps={{ | ||
courseId, | ||
}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not opposed to having courseId
in pluginProps
, but my general feeling is we should only add pluginProps
when we know that there is a use case that requires them. Adding something to pluginProps
makes it part of the plugin API, meaning removing it would require going through a DEPR process. Do you have an example use case in mind where courseId
would be required in this slot?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I will remove them from here since the courseId should be unambiguous in these slots and can probably be fetched from the store.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will eventually want to refactor every page like this in the Learning MFE to not use redux and to use React Context + React Query instead. So if you do need course ID, I would prefer plugins don't fetch it from the store, because that's implicitly making the Redux store part of the API contract here. Our redux stores tend to be messy and buggy because they don't even have a contract within each MFE (they aren't typed using TypeScript), and I really don't want them to become part of the plugin contract, whether officially or unofficially.
In general, my personal view is that plugin slots should pass any props that come from the URL, so that they know exactly which page they're on (in this case it would be the course ID), and any other data that they need should be loaded via small usages of React Query (which should be de-duplicated if any other places on the page need the same data).
In this case, the default content of the plugin slot
does need the course ID (e.g. "Dates" links to /learning/course/:courseId/dates
), so I think it makes sense to make that course ID available to any other plugins filling the slot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think perhaps it makes sense to standardise some kind of common context across all MFEs then and plugin can fetch data from there without needing props. I think courseId / learningContextId probably belongs in the context as it is by definition the context in which each component is rendered. At the bare minimum I think the shared common context should have the contextId
, username
etc. If the username is missing then a user isn't logged in and if the contextId is missing then it doesn't apply (for instance in the profile page or learner dashboard).
Or it could be as simple as having hooks like useContextId
which can then get it from the store right now and from the shared context or whatever mechanism we have later. It seems like too common a usecase here.
The removal of the courseId is in an isolated commit so I can revert easily, but, how about I instead add a useContextId
hook that is designed for any component or plugin to use? If it looks good we can standardise something like that as a common API>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think perhaps it makes sense to standardise some kind of common context across all MFEs then and plugin can fetch data from there without needing props. I think courseId / learningContextId probably belongs in the context as it is by definition the context in which each component is rendered.
Totally agree with this.
Or it could be as simple as having hooks like useContextId which can then get it from the store right now and from the shared context or whatever mechanism we have later. It seems like too common a usecase here.
Great idea. Maybe just call it "useLearningContextId" since "Context" is ambiguous to me.
</div> | ||
|
||
{/* Side panel */} | ||
<div className="col-12 col-md-4 p-0 px-md-4"> | ||
{wideScreen && <CertificateStatus />} | ||
<RelatedLinks /> | ||
{wideScreen && <ProgressTabCertificateStatusSlot courseId={courseId} />} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to hear others thoughts on having the ProgressTabCertificateStatusSlot
in 2 places here. The way this is written now would require plugin authors utilizing the ProgressTabCertificateStatusSlot
to account for the slot being in the side panel or main body depending on the width of the viewport.
I see a few options here:
-
A: Keep the slots as written in this PR
- Requires plugin authors to account for slots being in different places based on viewport width
-
B: Split
ProgressTabCertificateStatusSlot
intoProgressTabCertificateStatusMainBodySlot
andProgressTabCertificateStatusSidePanelSlot
- Requires site operators to put a plugin in 2 slots instead of 1 (even if it's the same plugin)
-
C: Switch to just having a
MainBody
slot and aSidePanel
slot instead of granular slots for each component in the body/panel- This would remove quite a bit of flexibility in customization
-
D: Some combination of (A or B) and C
- This would mean keeping the granular slots, but also wrapping those slots in a bigger slot - similar to how
frontend-component-header
has aDesktopHeaderSlot
that wraps the entire desktop header, while the desktop header component contains more granular slots such asDesktopUserMenuSlot
I think I lean towards B, with a possibility of adding bigger slots later if needed. I am, however, very open to other opinions on the matter, and would love to hear other options I haven't thought of that others have.
- This would mean keeping the granular slots, but also wrapping those slots in a bigger slot - similar to how
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
B seems reasonable to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps there is another option, which is to pass the slot position to component?
i.e. one of the props can be the "placement" of the content, which could be "mainbody" or "sidebar".
I imagine anyone creating a plugin for this pair of slots would either use the same component or have a single component dynamically adjust to the placement. By explicitly providing this value we can ensure that the plugin author will follow the same logic as the app even if that changes over time.
Otherwise, I agree that the two slot approach looks good, and I will adapt the code accordingly. I'll push the approach using the "placement" prop since it's a small change, but will switch to the two component approach if that seems better.
70b6f6e
to
08ef4f7
Compare
@brian-smith-tcril I've updated the slots to remove the courseId. I've updated the examples to pull the course ID from the state so that I wouldn't have to update the images though. I hope that's okay! |
); | ||
|
||
ProgressTabCertificateStatusSlot.propTypes = { | ||
placement: PropTypes.oneOf(['MAIN_BODY', 'SIDEBAR']), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how I feel about having placement
as a pluginProp
.
I think 2 slots provides more flexibility. With 2 slots, site operators can use different PLUGIN_OPERATIONS
for each, set keepDefault
differently for each, or set a different priority
for each.
With placement
as a pluginProp
that flexibility is lost.
That being said, I don't know if that flexibility is something we want. My current feeling is, "yes, we should provide the flexibility that comes with using 2 slots" - but I'm open to other opinions on the matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, I did not consider that someone might want to apply different plugin operations depending on placement.
On further thought I do have another issue with "placement" which is that currently it's not standardised. I do think there could be certain slots that would apply to multiple places in an app. In that case, it would be useful to have a standardised terminology for what it means for the placement to be in the "main body" or "sidebar" etc.
I'm wondering then if the logic of which version should be hidden should also be moved to the contents inside the plugin? For instance someone might want to always show it in the sidebar and others always in the main body?
I've updated the PR to use two components and moved the logic of hiding or showing the component to the slots so that a plugin can choose to use different logic. |
17027a6
to
0206392
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I really like how this has come together! I'd still like to have a bigger conversation about best practices for getting data into plugins (context vs props etc.), but I don't think that should block merging this.
It looks like CI is failing commitlint and some tests, once those are passing I'll give this a ✔️!
src/plugin-slots/ProgressTabCertificateStatusMainBodySlot/README.md
Outdated
Show resolved
Hide resolved
src/plugin-slots/ProgressTabCertificateStatusSidePanelSlot/README.md
Outdated
Show resolved
Hide resolved
f7d0919
to
1b24b8b
Compare
Adds a slot for different components in the progress tab to allow them to be overridden with custom components.
1b24b8b
to
5721f77
Compare
@brian-smith-tcril I've fixed the test issues, and the commitlint issues. Thanks for your thorough review! I feel starting with fewer props and adding more later is less disruptive so keeping courseId out of props seems good for now, and if things change, it will be easy to add it back without hurting existing plugins. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel starting with fewer props and adding more later is less disruptive
I 100% agree!
Thanks for all the work on this! It turned out great!
Are there screenshots and/or a description of this PR that I can share with a product manager or instructional designer? We have a backlog of pedagogical issues with the progress page, and I'm wondering if we can leverage the pluginslots to get the desired behavior. |
This doesn't change the UI at all, so it's hard to screenshot as-is, but I'm adding a screenshot that highlights all the elements that can now be altered with slots. |
@bradenmacdonald Did you want to give this another look before merging? If not, it looks like the changes will be ready to go after another rebase. |
@itsjeyd Nope, this is good to merge without further review from me. Thanks. |
Adds a slot for different components in the progress tab to allow them to be overridden with custom components.
The main aim here is to allow a client to support enabling/disabling individual components on a per-course basis. If this is a feature that will be valuable to the broader community we can change the implementation here to directly support that as well.
This change currently allows overriding individual components. If this is too granular we can also look into creating a slot for the entire page, however that will require more complexity and drift for the overriding component if it just wants to make minor changes.