Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
135 commits
Select commit Hold shift + click to select a range
70bece4
feat: add a new tag from frontend
jesperhodge Feb 12, 2026
e6d6cb7
feat: Add table control bar with expand button
jesperhodge Feb 12, 2026
e70becf
feat: create tags
jesperhodge Feb 19, 2026
3d9188b
feat: use react-table and get full depth of tags
jesperhodge Feb 19, 2026
39b8438
feat: support nested subrows in tag list table
jesperhodge Feb 20, 2026
ba08de0
feat: can create new tags with a subtag as parent
jesperhodge Feb 23, 2026
f11e813
feat: show add row conditionally on table depth
jesperhodge Feb 23, 2026
54b1175
test: make existing tag list table tests work again
jesperhodge Feb 23, 2026
de4e8c5
test: create tags
jesperhodge Feb 23, 2026
3ea6bed
test: generate tests from acceptance criteria
jesperhodge Feb 23, 2026
0f4dfda
test: add tests for nested sub-tags and taxonomy editability
jesperhodge Feb 23, 2026
15e9de1
feat: keep table working state with new row at top
jesperhodge Feb 24, 2026
ec926db
feat: add tag tree data structure
jesperhodge Feb 26, 2026
a617cb8
feat: create tag tree
jesperhodge Feb 26, 2026
f5f1ffc
fix: creating top tags
jesperhodge Feb 26, 2026
6021b82
feat: add card style
jesperhodge Feb 26, 2026
38df0b7
feat: add plus icon
jesperhodge Feb 26, 2026
9688d35
feat: add button styling
jesperhodge Feb 26, 2026
07c54d9
test: fix tests that are implemented
jesperhodge Feb 27, 2026
a459888
feat: add reducer for table modes
jesperhodge Feb 27, 2026
8db41e1
feat: enable preview mode
jesperhodge Feb 27, 2026
1081bfa
fix: mode transitions
jesperhodge Feb 27, 2026
cb2b3be
feat: add row options menu
jesperhodge Feb 27, 2026
7913057
test: skip anything thats not implemented yet
jesperhodge Feb 28, 2026
2f5202f
test: fix test
jesperhodge Feb 28, 2026
ee92b6c
refactor: change table mode name to preview
jesperhodge Mar 2, 2026
875ce1e
refactor: extract subcomponents
jesperhodge Mar 2, 2026
62a34d0
fix: tests
jesperhodge Mar 2, 2026
ddc5271
refactor: extract table display component
jesperhodge Mar 2, 2026
a522402
refactor: make table components reusable
jesperhodge Mar 2, 2026
f51f3ae
refactor: simplify and extract components
jesperhodge Mar 2, 2026
2b9aad0
refactor: extract reusable tree table components
jesperhodge Mar 2, 2026
3e7ac04
refactor: convert to typescript
jesperhodge Mar 2, 2026
e6caaa6
refactor: make tree table more readable
jesperhodge Mar 3, 2026
c2de45c
Merge remote-tracking branch 'upstream/master' into jhodge/create-tags
jesperhodge Mar 3, 2026
15d3c78
fix: delete duplicate file
jesperhodge Mar 4, 2026
1b630bb
feat: add expand icon
jesperhodge Mar 4, 2026
7c9c69b
fix: show columns with correct width
jesperhodge Mar 4, 2026
25c0397
fix: expand rows style
jesperhodge Mar 4, 2026
8fc888e
feat: tag list table expand and row UI
jesperhodge Mar 5, 2026
6bf6400
feat: add dropdown menu
jesperhodge Mar 5, 2026
9e3114a
feat: attempt to make editable row
jesperhodge Mar 5, 2026
784bf49
feat: move create top row buttons to right column
jesperhodge Mar 6, 2026
86522b1
feat: save create rows
jesperhodge Mar 6, 2026
1ca3c5a
feat: prettify expand all
jesperhodge Mar 6, 2026
cb093c5
fix: transitions and styles
jesperhodge Mar 6, 2026
6e887d5
feat: UI alignments
jesperhodge Mar 6, 2026
b3c298e
fix: lint
jesperhodge Mar 9, 2026
7cba21e
fix: lint
jesperhodge Mar 9, 2026
20ee272
fix: lint and types
jesperhodge Mar 9, 2026
3565051
fix: lint
jesperhodge Mar 9, 2026
0c645c7
refactor: remove unused code
jesperhodge Mar 9, 2026
694f5b9
Merge remote-tracking branch 'upstream/master' into jhodge/create-tags
jesperhodge Mar 9, 2026
4e1191f
feat: add Enter/Exit and spacing
jesperhodge Mar 9, 2026
27d47dc
fix: key press escape functionality
jesperhodge Mar 10, 2026
6a9e1db
fix: expand link
jesperhodge Mar 10, 2026
1dbf276
fix: style
jesperhodge Mar 10, 2026
bd6bbab
refactor: replace hardcoded pixel values with paragon/bootstrap sizes
jesperhodge Mar 10, 2026
674af1a
feat: improve accessibility
jesperhodge Mar 10, 2026
391ca25
fix: ui
jesperhodge Mar 10, 2026
3022311
fix: test
jesperhodge Mar 10, 2026
8b3d766
fix: tests
jesperhodge Mar 10, 2026
f7f2aaa
temp: disable pagination for tag list
jesperhodge Mar 10, 2026
d1ae67f
fix: tests
jesperhodge Mar 10, 2026
98aab9d
fix: lint
jesperhodge Mar 10, 2026
9da80a3
fix: pr review comments
jesperhodge Mar 10, 2026
afef299
fix: pr review comments
jesperhodge Mar 10, 2026
711a9b9
fix: pr review comments
jesperhodge Mar 10, 2026
4100fd6
fix: correct forbidden chars
jesperhodge Mar 10, 2026
e9c1c27
fix: lint
jesperhodge Mar 11, 2026
01120b0
fix: visual indent
jesperhodge Mar 11, 2026
a86679f
refactor: tests
jesperhodge Mar 11, 2026
9a9e3e2
refactor: tests
jesperhodge Mar 11, 2026
25af304
fix: test warnings
jesperhodge Mar 11, 2026
6e0366a
fix: disable behavior
jesperhodge Mar 11, 2026
a78e047
fix: test
jesperhodge Mar 11, 2026
e614c16
test: increase coverage
jesperhodge Mar 12, 2026
1f1e366
fix: lint
jesperhodge Mar 12, 2026
1ff082f
Merge remote-tracking branch 'upstream/master' into jhodge/create-tags
jesperhodge Mar 12, 2026
dc2db61
test: coverage
jesperhodge Mar 12, 2026
1cd7a44
fix: tests
jesperhodge Mar 12, 2026
85e6b7d
Merge remote-tracking branch 'upstream/master' into jhodge/tree-table…
jesperhodge Mar 16, 2026
9bdd5a4
fix: show correct number of taxonomy levels
jesperhodge Mar 17, 2026
a3f8899
refactor: extract constants
jesperhodge Mar 17, 2026
631d22d
fix: PR comments
jesperhodge Mar 17, 2026
407835d
fix: tests
jesperhodge Mar 18, 2026
d81af1b
Merge branch 'jhodge/create-tags' into jhodge/tree-table-base
jesperhodge Mar 18, 2026
faea987
fix: url parameter breaking things
jesperhodge Mar 18, 2026
4eb900b
Merge branch 'jhodge/create-tags' into jhodge/tree-table-base
jesperhodge Mar 18, 2026
b3d25bd
fix: PR comments and cleanup
jesperhodge Mar 25, 2026
6f2f6b9
fix: typo
jesperhodge Mar 25, 2026
3a96b58
fix: types
jesperhodge Mar 25, 2026
f193edb
fix: apply github code review suggestions
jesperhodge Mar 26, 2026
a8f8297
fix: PR comments
jesperhodge Mar 26, 2026
0971da4
fix: PR comments
jesperhodge Mar 26, 2026
825bab3
fix: tests
jesperhodge Mar 26, 2026
443221c
fix: tests
jesperhodge Mar 26, 2026
40f409e
fix: PR comment
jesperhodge Mar 26, 2026
4a4deef
fix: PR comments
jesperhodge Mar 26, 2026
38f87b8
fix: remove unused code
jesperhodge Mar 27, 2026
e8820a2
Merge remote-tracking branch 'upstream/master' into jhodge/create-tags
jesperhodge Mar 27, 2026
93605cd
Merge branch 'jhodge/create-tags' into temp--tag-table-base
jesperhodge Mar 27, 2026
726c5a7
feat: add button styling
jesperhodge Feb 26, 2026
7e0c15e
test: fix tests that are implemented
jesperhodge Feb 27, 2026
bfa7f72
feat: add reducer for table modes
jesperhodge Feb 27, 2026
b210695
feat: enable preview mode
jesperhodge Feb 27, 2026
14a036a
fix: mode transitions
jesperhodge Feb 27, 2026
ec0a690
feat: add row options menu
jesperhodge Feb 27, 2026
815d3d9
refactor: change table mode name to preview
jesperhodge Mar 2, 2026
3e8beba
refactor: extract subcomponents
jesperhodge Mar 2, 2026
c5b6916
refactor: extract table display component
jesperhodge Mar 2, 2026
f685672
refactor: make table components reusable
jesperhodge Mar 2, 2026
e54d369
refactor: simplify and extract components
jesperhodge Mar 2, 2026
942d374
refactor: extract reusable tree table components
jesperhodge Mar 2, 2026
6ffa46b
refactor: convert to typescript
jesperhodge Mar 2, 2026
be8dfd1
refactor: make tree table more readable
jesperhodge Mar 3, 2026
25a6d2c
fix: delete duplicate file
jesperhodge Mar 4, 2026
29b2af5
fix: expand rows style
jesperhodge Mar 4, 2026
160c5fa
feat: attempt to make editable row
jesperhodge Mar 5, 2026
3e3b3ce
feat: prettify expand all
jesperhodge Mar 6, 2026
67671d9
fix: transitions and styles
jesperhodge Mar 6, 2026
bb801cf
feat: #253 Initial commit for usage count display in taxonomy tags
tbain Mar 11, 2026
9cbc074
feat: #253 follow on commit to address GH Copilot suggestions
tbain Mar 11, 2026
1b7eee0
feat: #253 Add FE Unit tests, add invalidation logic on page load to …
tbain Mar 16, 2026
37eca33
refactor: extract constants
jesperhodge Mar 17, 2026
9de8c51
feat: #253 Removing superfluous file, fixing paged query not using in…
tbain Mar 18, 2026
a8e4544
feat: #253 Fixing lint issues
tbain Mar 24, 2026
dbb02f3
feat: #253 Adding FE Unit tests, addition to add invalidation logic o…
tbain Mar 16, 2026
0d25d49
feat: #253 Fixing lint issues
tbain Mar 24, 2026
aedde44
feat: #253 Refactoring refresh logic to fix erroneous refresh, issues…
tbain Mar 25, 2026
f0d4611
feat: #253 Updating branch with latest from upstream
tbain Mar 27, 2026
96d6061
Merge branch 'master' of https://github.com/openedx/frontend-app-auth…
tbain Mar 27, 2026
9dabba6
feat: #253 re-adding missing updates after merge from upstream
tbain Mar 27, 2026
e6ec144
Merge branch 'master' of https://github.com/openedx/frontend-app-auth…
tbain Apr 1, 2026
50812e2
feat: #253 Removing unneccessary file
tbain Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/library-authoring/library-info/LibraryInfo.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ describe('<LibraryInfo />', () => {
expect(screen.getByText('Settings')).toBeInTheDocument();
});

it('renders PublicReadToggle when user can manage team', async () => {
it('renders PublicReadToggle when user can manage team', async () => {
render();
const allowSwitch = await screen.findByRole('switch', { name: /allow public read/i });
expect(allowSwitch).toBeInTheDocument();
Expand Down
3 changes: 2 additions & 1 deletion src/taxonomy/data/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,13 @@ export const apiUrls = {
pageIndex, pageSize, fullDepth, disablePagination,
}: { pageIndex: number | null; pageSize: number | null; fullDepth?: boolean; disablePagination?: boolean }) => {
if (disablePagination) {
return makeUrl(`${taxonomyId}/tags/`, { full_depth_threshold: fullDepth ? MAX_TAXONOMY_ITEMS : 0 });
return makeUrl(`${taxonomyId}/tags/`, { full_depth_threshold: fullDepth ? MAX_TAXONOMY_ITEMS : 0, include_counts: 'true' });
}
return makeUrl(`${taxonomyId}/tags/`, {
page: (pageIndex ?? 0) + 1,
page_size: pageSize ?? 10,
full_depth_threshold: fullDepth ? MAX_TAXONOMY_ITEMS : 0,
include_counts: 'true',
});
},
/**
Expand Down
10 changes: 8 additions & 2 deletions src/taxonomy/data/apiHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { camelCaseObject } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { useIntl } from '@edx/frontend-platform/i18n';
import { apiUrls, ALL_TAXONOMIES, getApiErrorMessage } from './api';
import * as api from './api';
import type { QueryOptions, TagListData } from './types';
import { useIntl } from '@edx/frontend-platform/i18n';

// Query key patterns. Allows an easy way to clear all data related to a given taxonomy.
// https://github.com/openedx/frontend-app-admin-portal/blob/2ba315d/docs/decisions/0006-tanstack-react-query.rst
Expand Down Expand Up @@ -97,6 +97,7 @@ export const useDeleteTaxonomy = () => {
export const useTaxonomyDetails = (taxonomyId: number) => useQuery({
queryKey: taxonomyQueryKeys.taxonomyMetadata(taxonomyId),
queryFn: () => api.getTaxonomy(taxonomyId),
refetchOnMount: 'always',
});

/**
Expand Down Expand Up @@ -194,6 +195,7 @@ export const useTagListData = (taxonomyId: number, options: QueryOptions) => {
return camelCaseObject(data) as TagListData;
},
enabled,
refetchOnMount: 'always',
});
};

Expand Down Expand Up @@ -228,9 +230,13 @@ export const useCreateTag = (taxonomyId: number) => {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: taxonomyQueryKeys.taxonomyTagList(taxonomyId),
refetchType: 'none',
});
// In the metadata, 'tagsCount' (and possibly other fields) will have changed:
queryClient.invalidateQueries({ queryKey: taxonomyQueryKeys.taxonomyMetadata(taxonomyId) });
queryClient.invalidateQueries({
queryKey: taxonomyQueryKeys.taxonomyMetadata(taxonomyId),
refetchType: 'none',
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.

What's the reason for adding refetchType: 'none', here, and refetchOnMount: 'always', above?

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.

We had observed behavior where the taxonomy data wouldn't refresh when you navigated to the page, you'd have to do a manual browser refresh in order to get updated data. We had explored adding a small method to force it to refresh on mount, but refetchOnMount performs that same functionality without needing a somewhat hacky implementation. The refetchType: 'none' on the invalidate queries will prevent it from trying to refresh immediately upon invalidation and allow it to be refreshed naturally with the mount action.

Copy link
Copy Markdown
Contributor

@bradenmacdonald bradenmacdonald Apr 1, 2026

Choose a reason for hiding this comment

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

We had observed behavior where the taxonomy data wouldn't refresh when you navigated to the page, you'd have to do a manual browser refresh in order to get updated data.

Isn't that how all web pages normally work though? Or you mean somehow it was displaying stale data after previously displaying "new" data? If the latter, I think the "modes" code is affecting something.

The refetchType: 'none' on the invalidate queries will prevent it from trying to refresh immediately upon invalidation

Normally, we do want things to refresh immediately upon completion of some mutation, and that's how everything else in this MFE works. So this seems a bit unusual to me.

});
},
});
};
44 changes: 43 additions & 1 deletion src/taxonomy/tag-list/TagListTable.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const mockTagsResponse = {
descendant_count: 14,
_id: 1001,
sub_tags_url: '/request/to/load/subtags/1',
usage_count: 1,
},
{
...tagDefaults,
Expand All @@ -69,6 +70,7 @@ const mockTagsResponse = {
descendant_count: 10,
_id: 1002,
sub_tags_url: '/request/to/load/subtags/2',
usage_count: 0,
},
{
...tagDefaults,
Expand All @@ -77,6 +79,7 @@ const mockTagsResponse = {
descendant_count: 5,
_id: 1003,
sub_tags_url: '/request/to/load/subtags/3',
usage_count: 3,
},
{
...tagDefaults,
Expand All @@ -86,6 +89,7 @@ const mockTagsResponse = {
_id: 1111,
sub_tags_url: null,
parent_value: 'root tag 1',
usage_count: 1,
},
{
...tagDefaults,
Expand All @@ -95,6 +99,7 @@ const mockTagsResponse = {
_id: 1111,
sub_tags_url: null,
parent_value: 'the child tag',
usage_count: null,
},
],
};
Expand All @@ -107,7 +112,7 @@ const mockTagsPaginationResponse = {
start: 0,
results: [],
};
const rootTagsListUrl = 'http://localhost:18010/api/content_tagging/v1/taxonomies/1/tags/?full_depth_threshold=10000';
const rootTagsListUrl = 'http://localhost:18010/api/content_tagging/v1/taxonomies/1/tags/?full_depth_threshold=10000&include_counts=true';
const subTagsResponse = {
next: null,
previous: null,
Expand Down Expand Up @@ -217,6 +222,13 @@ describe('<TagListTable />', () => {
expect(rows.length).toBe(3 + 1); // 3 items plus header
expect(within(rows[0]).getAllByRole('columnheader')[0].textContent).toEqual('Tag name');
expect(within(rows[1]).getAllByRole('cell')[0].textContent).toEqual('root tag 1');
expect(within(rows[0]).getAllByRole('columnheader')[1].textContent).toEqual('Usage Count');
});

it('should render usage count correctly for root tag', async () => {
const rows = screen.getAllByRole('row');
expect(rows.length).toBe(3 + 1); // 3 items plus header
expect(within(rows[1]).getAllByRole('cell')[1].textContent).toEqual('1');
});

it('should render page correctly with subtags', async () => {
Expand All @@ -226,6 +238,36 @@ describe('<TagListTable />', () => {
expect(childTag).toBeInTheDocument();
});

it('should render usage count correctly for sub tag', async () => {
// Expand all tags and await for child tag to render
const expandButton = screen.getAllByText('Expand All')[0];
fireEvent.click(expandButton);
const childTag = await screen.findByText('the child tag');
expect(childTag).toBeInTheDocument();

const rows = screen.getAllByRole('row');
expect(rows.length).toBe(5 + 1); // 5 items plus header
expect(within(rows[2]).getAllByRole('cell')[1].textContent).toEqual('1');
});

it('should render usage count as empty/no content when usage count is "0"', async () => {
const rows = screen.getAllByRole('row');
expect(rows.length).toBe(3 + 1); // 3 items plus header
expect(within(rows[2]).getAllByRole('cell')[1].textContent).toEqual('');
});

it('should render usage count as empty/no when usage count is "null"', async () => {
// Expand all tags and await for child tag to render
const expandButton = screen.getAllByText('Expand All')[0];
fireEvent.click(expandButton);
const childTag = await screen.findByText('the child tag');
expect(childTag).toBeInTheDocument();

const rows = screen.getAllByRole('row');
expect(rows.length).toBe(5 + 1); // 5 items plus header
expect(within(rows[4]).getAllByRole('cell')[1].textContent).toEqual('');
});

it('should not render pagination footer if too few results', async () => {
axiosMock.onGet(rootTagsListUrl).reply(200, mockTagsResponse);
renderTagListTable();
Expand Down
2 changes: 0 additions & 2 deletions src/taxonomy/tag-list/TagListTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import React, {
useMemo,
useEffect,
} from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import type { PaginationState } from '@tanstack/react-table';
import { useTagListData, useCreateTag } from '../data/apiHooks';
import { TagTree } from './tagTree';
Expand Down Expand Up @@ -40,7 +39,6 @@ const TagListTable = ({ taxonomyId, maxDepth }: TagListTableProps) => {
// TODO: Simpler approaches have been suggested. Two options are to just use simple React state:
// `isCurrentlyEditingTag` and `lastCreatedTag`, or to use optimistic updates.
// For reference, see https://github.com/openedx/frontend-app-authoring/pull/2872#discussion_r2880965005.
const intl = useIntl();

const [creatingParentId, setCreatingParentId] = useState<RowId | null>(null);
const [editingRowId, setEditingRowId] = useState<RowId | null>(null);
Expand Down
4 changes: 4 additions & 0 deletions src/taxonomy/tag-list/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ const messages = defineMessages({
id: 'course-authoring.tag-list.column.value.header',
defaultMessage: 'Tag name',
},
tagListColumnCountHeader: {
id: 'course-authoring.tag-list.column.count.header',
defaultMessage: 'Usage Count',
},
tagListError: {
id: 'course-authoring.tag-list.error',
defaultMessage: 'Error: unable to load child tags',
Expand Down
21 changes: 20 additions & 1 deletion src/taxonomy/tag-list/tagColumns.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Bubble,
Button,
Icon,
IconButton,
Expand All @@ -24,6 +25,7 @@ interface TagListRowData extends TreeRowData {
depth: number;
childCount: number;
descendantCount: number;
usageCount?: number;
isNew?: boolean;
isEditing?: boolean;
}
Expand All @@ -47,6 +49,17 @@ interface GetColumnsArgs {
creatingParentId: RowId | null;
}

const UsageCountDisplay = ({ row }: { row: Row<TreeRowData> }) => {
const count = asTagListRowData(row).usageCount ?? 0;
return (
count > 0 && (
<Bubble expandable>
Comment thread
bradenmacdonald marked this conversation as resolved.
{count}
</Bubble>
)
);
};

interface ActionsHeaderProps {
onStartDraft: () => void;
setDraftError: (error: string) => void;
Expand Down Expand Up @@ -120,7 +133,7 @@ const ActionsMenu = ({ rowData, startSubtagDraft, disableAddSubtag }: ActionsMen
</Dropdown.Menu>
</Dropdown>
);
}
};

function getColumns({
setIsCreatingTopTag,
Expand All @@ -135,6 +148,7 @@ function getColumns({
}: GetColumnsArgs): TreeColumnDef[] {
const canAddSubtag = (row: Row<TreeRowData>) => row.depth < maxDepth;
const draftInProgressHintId = 'tag-list-draft-in-progress-hint';
const intl = useIntl();

return [
{
Expand All @@ -153,6 +167,11 @@ function getColumns({
);
},
},
{
id: 'count',
header: intl.formatMessage(messages.tagListColumnCountHeader),
cell: UsageCountDisplay,
},
{
id: 'actions',
header: () => (
Expand Down