Skip to content

feat: redesign Advanced Settings page with grouped sections and type-specific inputs#3019

Open
SantiagoSuHe wants to merge 10 commits intoopenedx:masterfrom
SantiagoSuHe:schema/advanced-settings-redesign-v2
Open

feat: redesign Advanced Settings page with grouped sections and type-specific inputs#3019
SantiagoSuHe wants to merge 10 commits intoopenedx:masterfrom
SantiagoSuHe:schema/advanced-settings-redesign-v2

Conversation

@SantiagoSuHe
Copy link
Copy Markdown

@SantiagoSuHe SantiagoSuHe commented Apr 23, 2026

Description

Redesigns the Advanced Settings page in Studio for course authors. Instead of a flat alphabetical list of ~82 settings, they are now organized into collapsible sections by functional category, with type-aware input components and a search/filter bar.

Screen.Recording.2026-04-23.at.5.00.17.PM.mov

User role affected: Course Author

What changed

New components:

  • SettingsFilters — search bar with collapse/expand all, and deprecated settings toggle
  • SettingsSection — collapsible section using Paragon's Collapsible.Advanced, renders settings grouped by category; supports subcategory grouping (used by Content Blocks → Settings / Problems / Video)
  • CourseDisplayOverrides — dedicated UI for the three display-override fields with enable/disable toggle and blocked state messaging
  • BooleanInput, StringInput, NumberInput, EnumInput, JsonInput — type-specific input components used by SettingCard

New data layer:

  • settingsCategories.ts — maps all settings keys to 13 categories with display order, exported constants, and subcategory map for Content Blocks
  • fieldTypes.tsgetFieldType() and serializeValue() utilities; ENUM_OPTIONS and FIELD_PLACEHOLDER_MESSAGES definitions
  • fieldTypeMessages.ts — i18n messages for enum labels and field placeholders
  • types.ts — shared TypeScript interfaces

Modified:

  • SettingCard.tsx — now renders type-specific input components instead of a single generic field; passes displayName as aria-label
  • AdvancedSettings.tsx — groups settings via groupSettingsByCategory(), renders one SettingsSection per category; data layer (React Query, API calls, selectors) is untouched
  • AdvancedSettings.scss — styles for section headers, collapsible triggers, and subcategory labels

Not modified: Redux store, API layer, thunks, selectors, routing.

Categories

13 sections: General Setting · Content Blocks · Grading · Schedule · Certificates · Enrollment Page · Pages & Resources · Special Exams · Mobile · Instructors · Legacy Discussion · Libraries · Other

Supporting information

This PR is motivated by two related initiatives:

  1. Schema's internal proposal — Relocating Advanced Settings (https://openedx.atlassian.net/wiki/spaces/COMM/pages/5236785155/Relocating+Advanced+Settings): we've been thinking about migrating the ~70 Advanced Settings in Studio into clearer homes across Studio's configuration, reducing reliance on the Advanced Settings page for tools that have matured. That broader migration is still the long-term direction.

  2. Discourse — Modernize UI options for Advanced Settings (https://discuss.openedx.org/t/modernize-ui-options-for-advanced-settings/17269): rather than starting with the larger migration, we think a better first step is to improve the usability of the page as it exists today. That's what this PR does. Moving individual settings to better homes can follow incrementally.

This is our first direct code contribution to the Open edX project from Schema, assisted by Claude Code. We're actively looking for feedback to improve our future contributions.

Testing instructions

  1. Start Studio and open any course.
  2. Go to Settings → Advanced Settings (or navigate directly to /course/{course_id}/settings/advanced).
  3. Verify settings appear grouped in collapsible sections by category.
  4. Expand and collapse individual sections.
  5. Use Collapse all / Expand all buttons in the filter bar.
  6. Type in the search box — verify results filter across all sections.
  7. Toggle Hide deprecated — deprecated settings should disappear.
  8. Find a Boolean setting and verify the toggle renders correctly.
  9. Find an Enum setting (e.g. showanswer) and verify the dropdown renders.
  10. Find a JSON setting and verify the CodeMirror editor renders.
  11. Edit a value, save, and reload — verify the value persists.
  12. Open with a screen reader and verify each input has a meaningful label (display name, not raw API key).

Other information

No new Redux state. No DB changes. No migrations. No feature flag needed (UI-only change within the existing advanced-settings subsystem).

../ relative imports remain in some new components (SettingsSection.tsx, CourseDisplayOverrides.tsx). Migrating to @src aliases is a follow-up item.

Best Practices Checklist

  • Any new files are using TypeScript (.ts, .tsx).
  • Avoid propTypes and defaultProps in any new or modified code.
  • Tests use the helpers in src/testUtils.tsx (initializeMocks).
  • Do not add new fields to the Redux state/store.
  • Use React Query to load data from REST APIs.
  • All new i18n messages in messages.ts files have a description.
  • Avoid using ../ in import paths. → Some ../ imports remain in new components — follow-up item.

…specific inputs

Redesigns the Advanced Settings page in Studio for course authors. Instead of a
flat alphabetical list of ~82 settings, settings are now organized into collapsible
sections by functional category, with type-aware input components and a search/filter bar.

New components:
- SettingsFilters: search bar with collapse/expand all and deprecated settings toggle
- SettingsSection: collapsible section using Paragon Collapsible.Advanced, renders
  settings grouped by category; supports subcategory grouping (Content Blocks)
- CourseDisplayOverrides: dedicated UI for display-override fields with enable/disable
  toggle and blocked state messaging
- BooleanInput, StringInput, NumberInput, EnumInput, JsonInput: type-specific inputs

New data layer:
- settingsCategories.ts: maps all settings keys to 13 categories with display order,
  exported constants, and subcategory map for Content Blocks
- fieldTypes.ts: getFieldType() and serializeValue() utilities; ENUM_OPTIONS definitions
- fieldTypeMessages.ts: i18n messages for enum labels and field placeholders
- types.ts: shared TypeScript interfaces (SettingData, SettingEntry, SetEditedSettings)

Modified:
- SettingCard.tsx: renders type-specific input components instead of a single generic
  field; passes displayName as aria-label for accessibility
- AdvancedSettings.tsx: groups settings via groupSettingsByCategory(), renders one
  SettingsSection per category; data layer (React Query, API calls, selectors) untouched
- AdvancedSettings.scss: styles for section headers, collapsible triggers, subcategory labels

Not modified: Redux store, API layer, thunks, selectors, routing.
@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 Apr 23, 2026
@openedx-webhooks
Copy link
Copy Markdown

Thanks for the pull request, @SantiagoSuHe!

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.

SantiagoSuHe and others added 4 commits April 22, 2026 22:00
…s.ts

- Run dprint fmt on all new advanced-settings files to comply with the
  project's code formatting configuration
- Fix i18n extraction error in src/generic/create-or-rerun-course/messages.ts:
  replace template literal defaultMessage with FormatJS {maxLength} placeholder;
  update hooks.jsx to pass maxLength value at runtime; update test assertion
Replace incorrect `as [string, object][]` cast with `as SettingEntry[]`
so the type checker can verify the prop against SettingsSection's expected
type. Also adds the missing `SettingEntry` type import.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

❌ Patch coverage is 96.61017% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.51%. Comparing base (47d213b) to head (109abc0).
⚠️ Report is 18 commits behind head on master.

Files with missing lines Patch % Lines
src/advanced-settings/AdvancedSettings.tsx 87.17% 5 Missing ⚠️
...nced-settings/settings-section/SettingsSection.tsx 93.84% 2 Missing and 2 partials ⚠️
src/advanced-settings/setting-card/SettingCard.tsx 95.74% 2 Missing ⚠️
...dvanced-settings/setting-card/inputs/EnumInput.tsx 94.73% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3019      +/-   ##
==========================================
+ Coverage   95.47%   95.51%   +0.03%     
==========================================
  Files        1383     1407      +24     
  Lines       32657    33232     +575     
  Branches     7259     7675     +416     
==========================================
+ Hits        31180    31742     +562     
+ Misses       1421     1420       -1     
- Partials       56       70      +14     

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

@bradenmacdonald
Copy link
Copy Markdown
Contributor

Wow, I didn't know this was happening and I'm so happy to see this PR!

Could you please add a screenshot or two to the PR description? And let me know when you'd like a review!
Are you trying to get this included in Verawood?

@SantiagoSuHe
Copy link
Copy Markdown
Author

Hi @bradenmacdonald thanks for your comment.
I added a video to the description section 👍

SantiagoSuHe and others added 2 commits April 23, 2026 21:14
- SettingCard: add tests for boolean, number, enum and JSON input
  rendering; handleImmediateChange for boolean/enum; catch branch in
  getDisplayValue when JSON.parse throws
- SettingsSection: add tests for forceOpen re-expand behaviour and
  CATEGORY_GENERAL_SETTING branch (CourseDisplayOverrides rendering)
- SettingsFilters: add test for onClear callback via form reset
- BooleanInput: add test for displayName || name aria-label fallback
- EnumInput: add tests for onBlur, displayName fallback, null value
  and unknown field name
- StringInput: add tests for default value param, displayName fallback
  and null value
- NumberInput: add tests for default value param and displayName fallback
- JsonInput: capture updateListener callback; add tests for user-driven
  onChange and programmatic dispatch suppression

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@mphilbrick211 mphilbrick211 moved this from Needs Triage to In Eng Review in Contributions Apr 29, 2026
Copy link
Copy Markdown
Contributor

@bradenmacdonald bradenmacdonald left a comment

Choose a reason for hiding this comment

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

Sorry for the slow response here, I haven't had time to fully test or review this in depth. I just had some high level questions about the approach for grouping the settings and the UI controls.

Comment on lines +2 to +3
* Field type classification for all Advanced Settings.
* Type is detected at runtime by getFieldType(key, value) based on typeof value + this enum map.
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.

Can't the backend provide the type information via the API? I don't like having to hard-code so much on the frontend.

And if we are hard-coding it on the frontend, what do you think about combining the "categories" and "field types" information?

You have

categories = {
 displayName: 'General Setting',
 ...
}

fieldTypes = {
   boolean: ...,
   enum: ...,
}

Wouldn't it be easier to understand as this?

const advancedSettings = [
  {
    name: "General Settings",
    fields: [
        {code: "displayName", type: "string", label: messages.displayNameField},
        {code: "courseEditMethod", type: "enum", values: [
          { value: 'Studio', label: 'Studio' },
          { value: 'XML', label: 'XML' },
        ], label: messages.courseEditMethodField},
    ],
  }
];

But better yet, can't we get the backend to send us this data ^ ?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We could - one intersting note here is that if we continue with other feature ideas -- we could migrate many / most of these categories away from this page in future releases, so not opposed to formalizing the categories but technically we have a feature idea to migrate all of these categories except for "Other"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Hi @bradenmacdonald Thanks for the feedback.

We also liked your idea of combining the categories and field types info, we went ahead and implemented that. Both files now derive all their data from a single settingsConfig.ts that groups each field with its category, type, and enum options in one place.

The reason we hardcoded the files on the frontend is that we intentionally wanted to keep the scope of this PR to frontend only changes, no backend modifications required to ship this redesign.

That said, we're also aware that many of these settings will eventually be migrated out of Advanced Settings into more appropriate places in Studio, so some of these categories may become irrelevant over time anyway.

We're fully open to doing this the right way if you think a backend driven approach is the better long term solution. If that's the direction, we're happy to open a PR in the platform repo to expose type/category metadata via the API. Just let us know how you'd like to proceed and we'll follow your lead.

@@ -0,0 +1,25 @@
import { Form } from '@openedx/paragon';

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.

All these new fields BooleanInput, EnumInput, JsonInput etc. are currently here in src/advanced-settings/setting-card/inputs/...`

Do you think we might want to re-use these components in other contexts like the component editor, library settings, or other places? Or are they really specific to the advanced settings UI?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

If a reuse opportunity comes up, we can extract them to a shared location at that point. For now, keeping them here avoids premature abstraction.

@bradenmacdonald
Copy link
Copy Markdown
Contributor

@brian-smith-tcril @arbrandes Just flagging this PR - I'm not sure if it's possible for either of you to work with @SantiagoSuHe and get it over the line for Verawood. I don't think I'll have much time to help between now and the cut. If so, that would be amazing, but it's not a release blocker or anything afaik.

Copy link
Copy Markdown
Contributor

@PKulkoRaccoonGang PKulkoRaccoonGang left a comment

Choose a reason for hiding this comment

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

Thanks for this PR - this is a significant improvement over the previous version. I've left a few comments, including one question regarding the empty search alert.

Have you considered showing an alert indicating that no results were found?

Image

margin-bottom: 1.75rem;
// Section is now a Card; individual setting rows replace per-setting Cards
.settings-section {
overflow: hidden;
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.

Do we really need to have overflow: hidden; on this element?

font-size: 1.125rem;
}
.sub-header {
padding-bottom: 1.5rem;
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.

Can we use Paragon Utility Classes instead of new styles?

const intl = useIntl();

return (
<div className="settings-filters d-flex align-items-center gap-3 mb-4">
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.

Could we use the Paragon Stack component here and remove the extra styles?

gap: .75rem;

.settings-filters-search {
width: 280px;
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.

Do we really need to hardcode the width for this element? I tried removing it and didn't notice any difference.

If there's indeed a need for it, we can use Paragon utility classes (e.g. w-25 or w-50)

return (
<Card className="settings-section mb-4">
<Collapsible.Advanced open={isOpen} onToggle={setIsOpen}>
<Collapsible.Trigger className="settings-section-trigger w-100 p-0 border-0 bg-transparent text-left">
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.

Do we really need all these CSS classes? I tried removing them and didn't notice any difference.

import NumberInput from './NumberInput';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const renderInput = (props: Record<string, any> = {}) =>
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.

Can we use NumberInputProps?

size="sm"
/>
</div>
<Button
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.

The Button component already has an iconAfter prop should we use it here to render the icon instead?

src={InfoOutline}
iconAs={Icon}
alt={intl.formatMessage(messages.courseDisplayOverridesInfoAlt)}
variant="primary"
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.

We can remove this. "Primary" is the default variant for IconButton.

alt={intl.formatMessage(messages.courseDisplayOverridesInfoAlt)}
variant="primary"
size="sm"
className="flex-shrink-0"
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.

Do we really need this CSS class?

];

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const renderSection = (props: Record<string, any> = {}) =>
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.

Can we use SettingsSectionProps?

SantiagoSuHe and others added 3 commits May 1, 2026 02:16
…onald

- Unify settingsCategories + fieldTypes into single settingsConfig.ts source of truth
- Replace custom SCSS with Paragon utility classes (mx-1, my-0, font-weight-bold, etc.)
- Use Paragon Stack component in SettingsFilters instead of manual flex layout
- Use Form.Control as="select" in EnumInput instead of raw <select>
- Use Button iconBefore prop instead of manual Icon child in SettingsFilters
- Remove redundant variant="primary" from IconButton (Paragon default)
- Remove unnecessary CSS classes confirmed to have no visual effect
- Add barrel file inputs/index.ts and import all inputs from it in SettingCard
- Migrate fireEvent → userEvent in all new test files
- Export and use typed interfaces (BooleanInputProps, EnumInputProps,
  NumberInputProps, SettingsSectionProps) in tests instead of Record<string, any>
- Add headerClassName prop to SubHeader for injecting utility classes
- Show Alert when search returns no results
- Add Badge mx-1 shorthand and other margin shorthands throughout

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@arbrandes arbrandes self-requested a review May 1, 2026 12:30
Copy link
Copy Markdown
Contributor

@arbrandes arbrandes left a comment

Choose a reason for hiding this comment

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

Let me start by saying that this looks and works great. It will be a much needed improvement, once it lands. BUT...

This introduces coupling where before there was none. Meaning, the settings metadata now lives in the frontend, hardcoded against the set of ~82 keys in settingsConfig.ts. That's a lot.

There are two concretely bad consequences of this coupling (one of them very bad):

  1. Drift. When edx-platform's set of Advanced Settings changes, whether because a new field is added or an existing one renamed, the frontend silently degrades: the unknown key lands in "Other" with type-by-value-shape detection, obviating the categorization this PR introduces.
  2. Data loss. If the backend ever adds a new showanswer value, the dropdown won't include it - and worse, if a course already has that value set, the <select> won't render it as the current selection. Silent data loss on save is possible. This is a new risk: pre-PR the user typed JSON directly, so any backend value round-tripped fine.

If it weren't for the second consequence, I might be convinced to merge this for the UX improvement alone. But in the end this implementation is not only just nice-to-have, but also obsolete on inception: it seems the real plan is to relocate the settings (and, presumably, guard them) to more logical places in Studio. Though it's likely that, as somebody apparently suggested, we will

"keep Advanced Settings as a lightweight space for experimental or rarely used configurations, since it allows the addition of new or temporary options without requiring new front-end development"

That means we will continue to need a generic solution, with no coupling. Might as well solve the problem now. 🤷🏼

Apologies for the rejection, particularly since this is such an obvious UX improvement. I'll be happy to continue reviewing once it's refactored so that the source of truth for the metadata comes entirely from the backend, though.

@SantiagoSuHe
Copy link
Copy Markdown
Author

Hi @arbrandes, thanks for the review and the clear explanation.
My initial approach was to make this PR frontend only to keep it simpler, but you're right, those two issues make it unmergeable as is.
I'll refactor so the metadata comes from the API and update the PR once it's ready.

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: In Eng Review

Development

Successfully merging this pull request may close these issues.

7 participants