Skip to content

feat: certificate regeneration flow with confirmation modals#197

Merged
wgu-jesse-stewart merged 13 commits into
openedx:mainfrom
WGU-Open-edX:wgu-jesse-stewart/111-certificates-fixes
May 12, 2026
Merged

feat: certificate regeneration flow with confirmation modals#197
wgu-jesse-stewart merged 13 commits into
openedx:mainfrom
WGU-Open-edX:wgu-jesse-stewart/111-certificates-fixes

Conversation

@wgu-jesse-stewart
Copy link
Copy Markdown
Contributor

@wgu-jesse-stewart wgu-jesse-stewart commented May 5, 2026

Description

This PR replaces the previous one-click certificate regeneration flow with explicit confirmation modals that adapt to the currently-selected learner filter, and adds a dedicated generation flow for granted exceptions with the option to skip learners who already have a certificate.

Screen.Recording.2026-05-12.at.12.28.51.PM.mov

Behavior changes:

  • The toolbar button is now disabled when the filter is set to All Learners or Invalidated, with a tooltip explaining what the operator needs to do (pick a specific learner group). Previously, clicking with All Learners would fire a regeneration against 'all' immediately.
  • For the Granted Exceptions filter, the button label changes to Generate Certificates and opens a new GenerateCertificatesModal with two radio options:
    • All users on the exception list (maps to student_set: 'allowlisted')
    • All users on the exception list who do not yet have a certificate (maps to student_set: 'allowlisted_not_generated')
  • For all other actionable filters (Received, Not Received, Audit - Passing, Audit - Not Passing, Error), the button opens a new RegenerateCertificatesModal with a filter-specific title and a confirmation message that includes the affected learner count.

Supporting changes:

  • regenerateCertificates (API) and useRegenerateCertificates (hook) now accept onlyWithoutCertificate so the granted-exceptions flow can pick the correct student_set. The hook's mutate signature changed from mutate(filter) to mutate({ filter, onlyWithoutCertificate }).
  • getErrorMessage was hardened to handle responses where response.data is either a string or an object, and to fall through error, message, and detail fields before using the fallback. This is what surfaces backend validation errors in the new modals' error path.
  • Removed the now-unused getFilterLabel helper and the regenerateCertificatesButtonWithFilter button-text variant from the toolbar.

User roles impacted: Operator / Course Staff (instructor dashboard users).

Fixes #{issue}

Supporting information

(Link Discourse / issue here if applicable.)

Testing instructions

  1. Open the Instructor Dashboard for a course with issued certificates, exceptions, and at least one learner in an error state.
  2. Navigate to the Certificates page, Issued Certificates tab.
  3. With the filter set to All Learners (default), confirm the regenerate button is disabled and that hovering it shows the tooltip directing you to pick a learner group.
  4. Switch the filter to Invalidated. Confirm the button is still disabled and shows the corresponding tooltip.
  5. Switch the filter to Received. Click Regenerate Certificates:
    • Confirm a modal opens with a title referencing learners who already received certificates and a message containing the correct learner count.
    • Click Cancel; modal should close with no API call.
    • Click the button again, then Regenerate; confirm a success toast appears and the API was called with student_set: 'verified' (or the appropriate set for that filter).
  6. Repeat step 5 for Not Received, Audit - Passing, Audit - Not Passing, and Error and confirm the modal copy adapts to each filter.
  7. Switch the filter to Granted Exceptions. Confirm the button label changes to Generate Certificates. Click it:
    • The GenerateCertificatesModal opens with two radio options, All Users on the Exception list selected by default.
    • Click Generate with the default selection; confirm the API is called with student_set: 'allowlisted'.
    • Reopen the modal, select All Users on the Exception list who do not yet have a certificate, click Generate; confirm the API is called with student_set: 'allowlisted_not_generated'.
  8. Force a backend error (e.g., stub a 500 response). Confirm the modal closes and an error alert appears with the server's message when one is provided, and with the localized fallback otherwise.
  9. Run unit tests: npm test -- src/certificates.

Other information

  • No backend or settings changes required; the new student_set: 'allowlisted_not_generated' value is already supported by the existing certificate regeneration endpoint contract.
  • Accessibility: the disabled button is wrapped in a span inside OverlayTrigger so the tooltip remains reachable while the button itself is non-interactive. Both modals use Paragon's ModalDialog and Form.RadioSet for keyboard and screen-reader support.
  • The useRegenerateCertificates hook signature change is a minor breaking change for any in-tree caller, but CertificatesPage is currently the only consumer.

Best Practices Checklist

  • Any new files are using TypeScript (.ts, .tsx).
  • Deprecated propTypes, defaultProps, and injectIntl patterns are not used in any new or modified code.
  • Tests should use the helpers in src/testUtils.tsx (specifically initializeMocks)
  • Use React Query to load data from REST APIs.
  • All new i18n messages in messages.ts files have a description for translators to use.
  • Imports avoid using ../.

 generate exception certificates modal
@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 May 5, 2026
@openedx-webhooks
Copy link
Copy Markdown

openedx-webhooks commented May 5, 2026

Thanks for the pull request, @wgu-jesse-stewart!

This repository is currently maintained by @openedx/committers-frontend-app-instructor-dashboard.

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.

@wgu-jesse-stewart wgu-jesse-stewart changed the title feat: add feat: certificate regeneration flow with confirmation modals May 5, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

❌ Patch coverage is 96.66667% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.76%. Comparing base (9ffd17a) to head (9e94c6e).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
src/certificates/CertificatesPage.tsx 93.84% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #197      +/-   ##
==========================================
+ Coverage   90.28%   90.76%   +0.48%     
==========================================
  Files         141      147       +6     
  Lines        2408     2621     +213     
  Branches      521      559      +38     
==========================================
+ Hits         2174     2379     +205     
- Misses        226      236      +10     
+ Partials        8        6       -2     

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

@wgu-jesse-stewart wgu-jesse-stewart marked this pull request as ready for review May 6, 2026 20:56
@wgu-jesse-stewart wgu-jesse-stewart moved this from Needs Triage to Ready for Review in Contributions May 6, 2026
Copy link
Copy Markdown
Contributor

@awais-ansari awais-ansari left a comment

Choose a reason for hiding this comment

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

Hardcoded strings in test cases if the defaultMessage changes, tests silently verify wrong content.

Comment thread src/certificates/components/RegenerateCertificatesModal.tsx Outdated
@wgu-jesse-stewart
Copy link
Copy Markdown
Contributor Author

Hardcoded strings in test cases if the defaultMessage changes, tests silently verify wrong content.

Thank you for the great feedback, @awais-ansari

@diana-villalvazo-wgu
Copy link
Copy Markdown
Contributor

can you add some screenshots on how this new implementation/changes looks?

Comment thread src/certificates/components/RegenerateCertificatesModal.tsx Outdated
Comment thread src/app.ts Outdated
Comment thread site.config.dev.tsx Outdated
Comment thread src/provides.ts
@diana-villalvazo-wgu
Copy link
Copy Markdown
Contributor

diana-villalvazo-wgu commented May 11, 2026

Demo
Screenshot 2026-05-11 at 5 50 44 p m
Figma
Screenshot 2026-05-11 at 5 46 15 p m
The main tabs on the demo doesn't look like the mocks on the figma file, the rounded border radius, color of the unselected tab text, etc , i guess because you are using it as tabs, you can use a button group to simplify it and just change variant if selected, if you don't want to get messy with styling

@wgu-jesse-stewart
Copy link
Copy Markdown
Contributor Author

Demo Screenshot 2026-05-11 at 5 50 44 p m Figma Screenshot 2026-05-11 at 5 46 15 p m The main tabs on the demo doesn't look like the mocks on the figma file, the rounded border radius, color of the unselected tab text, etc , i guess because you are using it as tabs, you can use a button group to simplify it and just change variant if selected, if you don't want to get messy with styling

It uses the Tabs component from Paragon, which I assume was the intended implementation. If @edschema flags it, I will change it. https://paragon-openedx.netlify.app/components/tabs/

@diana-villalvazo-wgu
Copy link
Copy Markdown
Contributor

Demo Screenshot 2026-05-11 at 5 50 44 p m Figma Screenshot 2026-05-11 at 5 46 15 p m The main tabs on the demo doesn't look like the mocks on the figma file, the rounded border radius, color of the unselected tab text, etc , i guess because you are using it as tabs, you can use a button group to simplify it and just change variant if selected, if you don't want to get messy with styling

It uses the Tabs component from Paragon, which I assume was the intended implementation. If @edschema flags it, I will change it. https://paragon-openedx.netlify.app/components/tabs/

As i mentioned on the prev message, you can use a button group to get the desired styles as figma (is also on paragon
https://paragon-openedx.netlify.app/components/button/button-group/ )

And for the unselected button use outline primary variant and for the selected one primary, that gets the same styles as the figma

@wgu-jesse-stewart
Copy link
Copy Markdown
Contributor Author

Demo Screenshot 2026-05-11 at 5 50 44 p m Figma Screenshot 2026-05-11 at 5 46 15 p m The main tabs on the demo doesn't look like the mocks on the figma file, the rounded border radius, color of the unselected tab text, etc , i guess because you are using it as tabs, you can use a button group to simplify it and just change variant if selected, if you don't want to get messy with styling

It uses the Tabs component from Paragon, which I assume was the intended implementation. If @edschema flags it, I will change it. https://paragon-openedx.netlify.app/components/tabs/

As i mentioned on the prev message, you can use a button group to get the desired styles as figma (is also on paragon https://paragon-openedx.netlify.app/components/button/button-group/ )

And for the unselected button use outline primary variant and for the selected one primary, that gets the same styles as the figma

@diana-villalvazo-wgu - done.

I was looking at the Grading and Special Exams implementations as examples for how to handle the tab styling issue in Certificates, and I wanted to flag some accessibility gaps I noticed with the ButtonGroup approach.
The current pattern is missing a few key pieces that screen reader and keyboard users need:

  • Missing role="tablist" on the ButtonGroup wrapper - without this, assistive tech can't tell these buttons are actually tabs
  • Missing ARIA relationships - tabs need aria-controls pointing to their panels, and panels need aria-labelledby pointing back to their tabs
  • Missing roving tabindex - right now all buttons stay in the tab order, but the pattern should have tabIndex={0} on the active tab and tabIndex={-1} on inactive ones
  • Missing keyboard navigation - arrow keys, Home, and End should move between tabs (this is required by WCAG 2.1 and the ARIA Authoring Practices Guide)
  • Panels need role="tabpanel" and hidden attribute - conditional rendering alone doesn't properly communicate the state to screen readers

I know we're working around the Paragon styling issue, but if we're replacing with custom buttons, we should implement the full accessible tab pattern - otherwise we're trading a temporary styling problem for a permanent accessibility one. I went ahead and implemented the full pattern here if you want to use it as a reference for updating Grading and Special Exams.

@diana-villalvazo-wgu
Copy link
Copy Markdown
Contributor

can you update the demo?

@wgu-jesse-stewart
Copy link
Copy Markdown
Contributor Author

can you update the demo?

updated

@wgu-jesse-stewart wgu-jesse-stewart enabled auto-merge (squash) May 12, 2026 16:29
@wgu-jesse-stewart wgu-jesse-stewart merged commit a1d315c into openedx:main May 12, 2026
6 checks passed
@github-project-automation github-project-automation Bot moved this from Ready for Review to Done in Contributions May 12, 2026
@diana-villalvazo-wgu diana-villalvazo-wgu deleted the wgu-jesse-stewart/111-certificates-fixes branch May 12, 2026 16:31
@openedx-semantic-release-bot
Copy link
Copy Markdown

🎉 This PR is included in version 1.0.0-alpha.40 🎉

The release is available on:

Your semantic-release bot 📦🚀

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 released on @latest

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants