Skip to content

Rename taxonomy tags#2939

Merged
bradenmacdonald merged 9 commits intoopenedx:masterfrom
jesperhodge:jhodge/edit-delete-tags
Apr 6, 2026
Merged

Rename taxonomy tags#2939
bradenmacdonald merged 9 commits intoopenedx:masterfrom
jesperhodge:jhodge/edit-delete-tags

Conversation

@jesperhodge
Copy link
Copy Markdown
Contributor

@jesperhodge jesperhodge commented Mar 12, 2026

Description

Implementing openedx/modular-learning#259 which is part of openedx/modular-learning#131.

Acceptance Criteria are outlined in the ticket.

This is pointing to branch temp--tag-table-base so the PR can be reviewed and the diff seen. Logically it depends on #2872 and includes that branch's changes, so we should wait with merge until 2872 is merged and then change the target branch to master.

Use of AI Tools

I used ChatGPT via Github Copilot to support this PR. Generally all submitted code has been reviewed by me as human as well. ChatGPT has mainly helped writing tests, where:

  • I took acceptance criteria from the ticket
  • I had ChatGPT convert acceptance criteria into test names that describe expected behavior
  • I reviewed test names to ensure expected behavior is covered
  • I had ChatGPT write some of the bodies of the tests
  • I carefully reviewed test expectation to make sure they match expected behavior and don't have fals positives
  • I manually tested the behavior

Testing instructions Step 1

  • Go to course authoring -> taxonomies -> taxonomy detail view
  • Go to a course and apply tags that you plan to rename (including subtags)
  • Rename a parent tag
  • Rename a subtag
  • Check that renamed tags remain in place where they were renamed
  • Reload page
  • Check that renamed tags still exist and that they are sorted alphabetically
  • Go back to course
  • Check that applied tags appear under the new name
  • Check that when a parent tag has been renamed its children show as part of the parent tag

Testing instructions Step 2

Test against the ACs in the ticket.

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

openedx-webhooks commented Mar 12, 2026

Thanks for the pull request, @jesperhodge!

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 Mar 12, 2026
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Waiting on Author in Contributions Mar 12, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 17, 2026

Codecov Report

❌ Patch coverage is 98.21429% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.57%. Comparing base (16d1903) to head (f32bf35).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
src/taxonomy/tag-list/hooks.ts 94.73% 1 Missing ⚠️
src/taxonomy/tag-list/tagColumns.tsx 93.33% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##           master    #2939    +/-   ##
========================================
  Coverage   95.56%   95.57%            
========================================
  Files        1349     1347     -2     
  Lines       31159    31247    +88     
  Branches     6833     7096   +263     
========================================
+ Hits        29777    29863    +86     
+ Misses       1332     1322    -10     
- Partials       50       62    +12     

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

@jesperhodge jesperhodge changed the base branch from master to temp--tag-table-base March 24, 2026 15:07
@jesperhodge jesperhodge marked this pull request as ready for review March 24, 2026 15:10
@jesperhodge jesperhodge changed the title Edit and Delete taxonomy tags Rename taxonomy tags Mar 24, 2026
@jesperhodge
Copy link
Copy Markdown
Contributor Author

I'll add the missing tests for coverage.

@kdmccormick kdmccormick self-requested a review March 24, 2026 15:23
Comment thread src/taxonomy/tag-list/TagListTable.test.jsx
.map((cell, index) => {
const content = flexRender(cell.column.columnDef.cell, cell.getContext());
const isFirstColumn = index === 0;
{editingRowId === `${row.original.id}:${String(row.original.value)}` ? (
Copy link
Copy Markdown
Contributor Author

@jesperhodge jesperhodge Mar 24, 2026

Choose a reason for hiding this comment

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

Review comment copied over from older review:

(Reviewer: @mgwozdz-unicon) I think there should be a unit test to check both these cases

@jesperhodge
Copy link
Copy Markdown
Contributor Author

I'll update the tests & make sure coverage is sufficient.

@kdmccormick kdmccormick added the create-sandbox open-craft-grove should create a sandbox environment from this PR label Mar 24, 2026
@kdmccormick
Copy link
Copy Markdown
Member

I'm on this PR's sandbox and I don't see any way to create or edit tags. Am I missing a change, or maybe a permission?

This is using the admin user (UN/PW=openedx/openedx).

Screenshot 2026-03-24 at 2 58 11 PM

@kdmccormick
Copy link
Copy Markdown
Member

@jesperhodge mind rebasing onto master now that the parent PR is merged?

@jesperhodge
Copy link
Copy Markdown
Contributor Author

@jesperhodge mind rebasing onto master now that the parent PR is merged?

After it's merged, yes! (still waiting. #2872)

@jesperhodge jesperhodge changed the base branch from temp--tag-table-base to master March 27, 2026 16:43
@bradenmacdonald
Copy link
Copy Markdown
Contributor

Ping me once this is ready for review :)

@jesperhodge jesperhodge force-pushed the jhodge/edit-delete-tags branch from d439387 to eaa833e Compare March 27, 2026 20:31
@bradenmacdonald
Copy link
Copy Markdown
Contributor

The annoying text selection bug is making renames not work properly. As soon as I type a single letter, it selects the whole value, and the second letter I type then erases everything. Could you please add a fix to this PR or open a separate PR with the fix?

Screen.Recording.2026-03-30.at.2.56.29.PM.mov

It may not be clear, but I put my cursor after "Cognitive" then press "/" and right after I do that it selects the whole value for some reason, causing my next keystroke to erase it all.

@bradenmacdonald
Copy link
Copy Markdown
Contributor

If I rename a tag to give it a name that already exists elsewhere in the table, the whole UI goes crazy. It seems like our API is incorrectly returning an HTML result in this case :/ So this is likely something we should fix on the backend too.

Screenshot 2026-03-30 at 3 09 38 PM

Comment thread src/taxonomy/tag-list/tagColumns.tsx Outdated
{intl.formatMessage(messages.addSubtag)}
</Dropdown.Item>
<Dropdown.Item
as={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.

Suggested change
as={Button}

Is there a reason for the as=Button here, and on the one above? It's causing the menu items to be centered, which looks bad and is inconsistent with all the other dropdown menus in the UI.

This Other
Image Image

onClick={editTag}
disabled={disableEditTag}
>
{intl.formatMessage(messages.renameTag)}
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.

Suggested change
{intl.formatMessage(messages.renameTag)}
<FormattedMessage {...messages.renameTag} />

Nit: it's not at all a big deal, but the react-intl docs recommend using declarative over imperative whenever possible. (Basically, only use intl.format...() when you need to set an HTML attribute like aria-label or a string-only prop.

Same thing on the "Add Subtag" entry above.

Comment thread src/taxonomy/tag-list/tagColumns.tsx Outdated
Comment on lines +214 to +219
onStartDraft();
setDraftError('');
setEditingRowId(`${rowData.id}:${rowData.value}`);
setCreatingParentId(null);
setIsCreatingTopTag(false);
setActiveActionMenuRowId(null);
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.

Nit: this seems a little fragile that you have to remember to change all these separate states when starting an edit action. Since you implemented VIEW and DRAFT modes with transition states, I would expect that "editing a tag" involves transitioning from (any state) to DRAFT, and that the state machine would take care of all these detailed things for us as part of that transition.

However, instead of that, I would strongly recommend that you create a TagEditorContext context that is shared among all these components, and give it a type such that it cannot have both editingRowId and creatingParentId set at the same time.

export interface TagEditorContext {
  activeEditType:
  | { rename: { id: number, value: string } }
  | { createTag: { parentTagId: number | null } }
}

That way, Typescript enforces for you that only one edit type is possible at any given time. And you don't have to pass things like setCreatingParentId around so much (it shows up 26 times in the code) - you can just pull it from the context wherever needed.

Comment on lines +176 to +180
act(() => {
if (expandButton) {
fireEvent.click(expandButton);
}
});
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.

Suggested change
act(() => {
if (expandButton) {
fireEvent.click(expandButton);
}
});
if (expandButton) {
fireEvent.click(expandButton);
}

You do not need to call act when using RTL APIs like fireEvent, because RTL APIs call act for you internally.

Also, nit: using user-event is preferred over using fireEvent directly. Like this:

user = userEvent.setup();
renderComponent();
formSections = screen.getAllByRole('textbox');
await user.type(formSections[0], 'email@email.com');
await user.type(formSections[1], 'test name');
await user.type(formSections[2], 'feedback message');

No need to change existing tests, but please prefer userEvent in new tests that you write. You should also be able to ask Claude/Gemini/ChatGPT to refactor it and it should do it very quickly and easily.

const openActionsMenuForTag = (tagName, actionButtonName = /actions/i) => {
if (!screen.queryAllByText(tagName)?.length) {
// expand all
const expandButton = screen.queryAllByText('Expand All')?.[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.

Suggested change
const expandButton = screen.queryAllByText('Expand All')?.[0];
const expandButton = screen.queryByRole('button', { name: 'Expand All' });

If you're specifically selecting a button in your test code, please use the *ByRole queries. It's more efficient, more accurate, and less likely to break if something else changes elsewhere on the page (e.g. a non-button thing is added that says "Click on 'Expand All' to see more" - that would break your test if it came before the button in the DOM).

Also using queryAll...(...)?.[0] won't throw an error if more than one element matches your query, whereas queryByRole will, while it also handles zero matches correctly (no error). So using queryAll...(...)?.[0] is bad, because e.g. if someone adds another button earlier in the DOM, it would make this test start failing and be harder to debug without a clear error explaining that there are multiple matches.

Comment on lines +206 to +207
fireEvent.click(screen.getByText('Rename'));
const input = screen.getByRole('textbox');
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.

Suggested change
fireEvent.click(screen.getByText('Rename'));
const input = screen.getByRole('textbox');
fireEvent.click(screen.getByRole('button', { name: 'Rename' }));
const input = screen.getByRole('textbox', { name: /type tag name/i });

Selectors like this ensure that we're using the correct accessible roles and labels on our UI elements. In particular, we want to be very sure that the textbox is labelled ("type tag name") for non-visual users, so writing your tests this way ensures that.

I think you may need to prompt your copilot to follow the RTL guiding principles and query priority recommendations because it doesn't seem to be aware of them.

Comment thread src/taxonomy/tag-list/TagListTable.test.jsx Outdated
</td>
))}
</tr>
{editingRowId === `${row.original.id}:${String(row.original.value)}` ? (
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.

Nit: I'd prefer we use a proper data structure to hold the ID and value of the row we're editing rather than this colon-separated string format.

Comment thread src/taxonomy/data/api.ts
@@ -90,6 +90,7 @@ export const apiUrls = {
/** URL to plan (preview what would happen) a taxonomy import */
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There's plenty of horizontal space here. Could we make the text input wider, so that it's more often able to show the whole tag during editing?

Fine to ticket up for a followup PR if you'd rather do that.

View:
Screenshot 2026-03-31 at 10 14 15 AM

Edit:
Screenshot 2026-03-31 at 10 15 25 AM

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes! Good catch.
Added it to openedx/modular-learning#256

@jesperhodge
Copy link
Copy Markdown
Contributor Author

If I rename a tag to give it a name that already exists elsewhere in the table, the whole UI goes crazy. It seems like our API is incorrectly returning an HTML result in this case :/ So this is likely something we should fix on the backend too.

@bradenmacdonald yup already got a subtask for it openedx/modular-learning#261

@jesperhodge
Copy link
Copy Markdown
Contributor Author

Hi @bradenmacdonald !

Thanks for the review. Here's what I did:
I fixed all the comments that were not nits

Because of our strong time constraints, I left anything that's a nit. I would have liked to look into some of these but I just have to prioritize some other features.

Are any of those nits particularly important to you? Which ones? I'm hoping we can make a low-prio ticket so hopefully I can take care of it when I finally have a moment of slack.
Particularly the context - I'd also like to introduce a context, which I haven't been able to due to time constraints.

What do you think?

@bradenmacdonald
Copy link
Copy Markdown
Contributor

Particularly the context - I'd also like to introduce a context, which I haven't been able to due to time constraints.

Yes, I would really appreciate if you can follow up on that soon. I wish we'd started with that pattern, which is what we're using for all the other new features in this codebase.

@jesperhodge
Copy link
Copy Markdown
Contributor Author

Particularly the context - I'd also like to introduce a context, which I haven't been able to due to time constraints.

Yes, I would really appreciate if you can follow up on that soon. I wish we'd started with that pattern, which is what we're using for all the other new features in this codebase.

Noted for the future!

@bradenmacdonald
Copy link
Copy Markdown
Contributor

UX nit for future follow-up: if you have a tag rename active, then the "Add subtags" and "Rename tag" actions on other tags are disabled. If the rename is off-screen, this may be confusing. I would expect that those options are still available, and selecting either one just cancels the current rename attempt.

Screenshot 2026-04-02 at 9 26 20 AM

@jesperhodge
Copy link
Copy Markdown
Contributor Author

jesperhodge commented Apr 2, 2026

I wish we'd started with that pattern, which is what we're using for all the other new features in this codebase.

Hi @bradenmacdonald , I hear you. It would have indeed been good.

For the future: what's the best way to get feedback like that from you in time to do larger refactorings like introducing a context? Should I keep asking for early-stage feedback on draft PRs like I did for create-tags, or is there a better way?

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.

OK, just a couple super minor things and then I'll merge this.

Comment thread src/taxonomy/tag-list/TagListTable.test.jsx Outdated
Comment thread src/taxonomy/tag-list/tagColumns.tsx Outdated
@bradenmacdonald
Copy link
Copy Markdown
Contributor

what's the best way to get feedback like that from you in time to do larger refactorings like introducing a context? Should I keep asking for early-stage feedback on draft PRs like I did for create-tags, or is there a better way?

Yes, I guess that's really the only option. But also you'll get more familiar with the patterns we use over time, like React Context, React Query, and React Testing Library. None of those are specific to Open edX, so there's also a lot of general purpose documentation and knowledge out there to learn from. e.g. the React docs are great, if you haven't read them: https://react.dev/learn/passing-data-deeply-with-context

@jesperhodge
Copy link
Copy Markdown
Contributor Author

I posted the question in slack, but for transparency just copying it here:

Before I hand the "Rename tags" PR back into review, I just wanted to check:
The taxonomy REST API sends a "canAddTag" attribute on the taxonomy level as a whole, but "canDelete" or "canChange" attributes are only specified on row levels.
I'm interpreting that as "if the user doesn't have the right to add tags on the whole taxonomy, if it's not specified, we assume they don't have rights to rename or delete either". Is that correct or should I change the frontend expectation?

@jesperhodge
Copy link
Copy Markdown
Contributor Author

Thanks @bradenmacdonald ! I fixed the two outstanding things you mentioned.
And I created a ticket to add the Context: openedx/modular-learning#271

@mphilbrick211 mphilbrick211 moved this from Waiting on Author to In Eng Review in Contributions Apr 6, 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.

One last thing, I think you left this in accidentally.

Comment thread src/taxonomy/tag-list/hooks.ts Outdated
@bradenmacdonald bradenmacdonald enabled auto-merge (squash) April 6, 2026 21:56
@bradenmacdonald bradenmacdonald merged commit bce8843 into openedx:master Apr 6, 2026
7 of 8 checks passed
@github-project-automation github-project-automation Bot moved this from In Eng Review to Done in Contributions Apr 6, 2026
@jesperhodge
Copy link
Copy Markdown
Contributor Author

Links to issue: openedx/modular-learning#131

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

Labels

create-sandbox open-craft-grove should create a sandbox environment from this PR open-source-contribution PR author is not from Axim or 2U

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants