Skip to content

Commit 958fe36

Browse files
committed
feat: add delete menu option
1 parent 57b770b commit 958fe36

3 files changed

Lines changed: 89 additions & 11 deletions

File tree

src/taxonomy/tag-list/TagListTable.test.jsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,44 @@ describe('<TagListTable />', () => {
991991
expect(within(grandchildTagRow).getByText('Add Subtag')).toHaveAttribute('aria-disabled', 'true');
992992
});
993993
});
994+
995+
describe('Delete Tags', () => {
996+
const tagDepthScenarios = [
997+
{
998+
description: 'Delete a top-level tag',
999+
tagName: 'root tag 1',
1000+
},
1001+
{ description: 'Delete a sub-tag', tagName: 'the child tag' },
1002+
{ description: 'Delete a grandchild tag', tagName: 'the grandchild tag' },
1003+
];
1004+
1005+
tagDepthScenarios.forEach(({ description, tagName }) => {
1006+
describe(description, () => {
1007+
beforeEach(async () => {
1008+
axiosMock.resetHistory();
1009+
});
1010+
1011+
it('should disable delete action and show tooltip if tag includes `can_delete: false`', async () => {
1012+
axiosMock.reset();
1013+
axiosMock.onGet(rootTagsListUrl).reply(200, mockTagResponseDisallowingEdits);
1014+
axiosMock.onGet(subTagsUrl).reply(200, subTagsResponse);
1015+
cleanup();
1016+
({ axiosMock } = initializeMocks({ user: adminUser }));
1017+
axiosMock.onGet(rootTagsListUrl).reply(200, mockTagResponseDisallowingEdits);
1018+
axiosMock.onGet(subTagsUrl).reply(200, subTagsResponse);
1019+
renderTagListTable();
1020+
await waitForRootTag();
1021+
1022+
openActionsMenuForTag(tagName);
1023+
const deleteButton = screen.getByRole('button', { name: /Delete/i });
1024+
expect(deleteButton).toBeInTheDocument();
1025+
expect(deleteButton).toHaveAttribute('aria-disabled', 'true');
1026+
fireEvent.mouseOver(deleteButton);
1027+
expect(screen.getByText(/This tag does not allow deletion/i)).toBeInTheDocument();
1028+
});
1029+
});
1030+
});
1031+
});
9941032
});
9951033

9961034
// These async creation flows are intentionally isolated because they pass individually

src/taxonomy/tag-list/messages.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ const messages = defineMessages({
6565
id: 'course-authoring.tag-list.rename-tag',
6666
defaultMessage: 'Rename',
6767
},
68+
deleteTag: {
69+
id: 'course-authoring.tag-list.delete-tag',
70+
defaultMessage: 'Delete',
71+
},
72+
deleteTagDisabledTooltip: {
73+
id: 'course-authoring.tag-list.delete-tag-disabled-tooltip',
74+
defaultMessage: 'This tag does not allow deletion',
75+
},
6876
});
6977

7078
export default messages;

src/taxonomy/tag-list/tagColumns.tsx

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {
33
IconButton,
44
IconButtonWithTooltip,
55
Dropdown,
6+
OverlayTrigger,
7+
Tooltip,
68
} from '@openedx/paragon';
79
import {
810
AddCircle,
@@ -25,24 +27,21 @@ import { getTagListRowData } from './utils';
2527
const EDITABLE_COLUMNS = ['value'];
2628

2729
interface GetColumnsArgs {
28-
setIsCreatingTopTag: (isCreating: boolean) => void;
30+
setIsCreatingTopRow: (isCreating: boolean) => void;
2931
setCreatingParentId: (id: RowId | null) => void;
30-
handleUpdateTag: (value: string, originalValue: string) => void;
3132
setEditingRowId: (id: RowId | null) => void;
3233
onStartDraft: () => void;
3334
setActiveActionMenuRowId: (id: RowId | null) => void;
3435
hasOpenDraft: boolean;
3536
canAddTag: boolean;
36-
draftError: string;
3737
setDraftError: (error: string) => void;
38-
isSavingDraft: boolean;
3938
maxDepth: number;
4039
}
4140

4241
interface ActionsHeaderProps {
4342
onStartDraft: () => void;
4443
setDraftError: (error: string) => void;
45-
setIsCreatingTopTag: (isCreating: boolean) => void;
44+
setIsCreatingTopRow: (isCreating: boolean) => void;
4645
setEditingRowId: (id: RowId | null) => void;
4746
setActiveActionMenuRowId: (id: RowId | null) => void;
4847
hasOpenDraft: boolean;
@@ -53,7 +52,7 @@ interface ActionsHeaderProps {
5352
const ActionsHeader = ({
5453
onStartDraft,
5554
setDraftError,
56-
setIsCreatingTopTag,
55+
setIsCreatingTopRow,
5756
setEditingRowId,
5857
setActiveActionMenuRowId,
5958
hasOpenDraft,
@@ -72,7 +71,7 @@ const ActionsHeader = ({
7271
onClick={() => {
7372
onStartDraft();
7473
setDraftError('');
75-
setIsCreatingTopTag(true);
74+
setIsCreatingTopRow(true);
7675
setEditingRowId(null);
7776
setActiveActionMenuRowId(null);
7877
}}
@@ -90,6 +89,9 @@ interface ActionsMenuProps {
9089
editTag: () => void;
9190
disableEditTag: boolean;
9291
reachedMaxDepth: (row: Row<TreeRowData>) => boolean;
92+
deleteTag: () => void;
93+
disableDeleteTag: boolean;
94+
9395
row: Row<TreeRowData>;
9496
}
9597

@@ -101,9 +103,20 @@ const ActionsMenu = ({
101103
editTag,
102104
disableEditTag,
103105
reachedMaxDepth,
106+
deleteTag,
107+
disableDeleteTag,
104108
}: ActionsMenuProps) => {
105109
const intl = useIntl();
106110

111+
const deleteTagMenuItem = (
112+
<Dropdown.Item
113+
onClick={deleteTag}
114+
disabled={disableDeleteTag}
115+
>
116+
{intl.formatMessage(messages.deleteTag)}
117+
</Dropdown.Item>
118+
)
119+
107120
return (
108121
<Dropdown>
109122
<Dropdown.Toggle
@@ -128,13 +141,25 @@ const ActionsMenu = ({
128141
>
129142
{intl.formatMessage(messages.renameTag)}
130143
</Dropdown.Item>
144+
{disableDeleteTag ? (
145+
<OverlayTrigger
146+
placement="bottom"
147+
overlay={
148+
<Tooltip id={`tooltip-taxonomy-delete-tag-${rowData.id}`}>
149+
{intl.formatMessage(messages.deleteTagDisabledTooltip)}
150+
</Tooltip>
151+
}
152+
>
153+
{deleteTagMenuItem}
154+
</OverlayTrigger>
155+
) : deleteTagMenuItem}
131156
</Dropdown.Menu>
132157
</Dropdown>
133158
);
134159
};
135160

136161
function getColumns({
137-
setIsCreatingTopTag,
162+
setIsCreatingTopRow,
138163
setCreatingParentId,
139164
setEditingRowId,
140165
onStartDraft,
@@ -175,7 +200,7 @@ function getColumns({
175200
<ActionsHeader
176201
onStartDraft={onStartDraft}
177202
setDraftError={setDraftError}
178-
setIsCreatingTopTag={setIsCreatingTopTag}
203+
setIsCreatingTopRow={setIsCreatingTopRow}
179204
setEditingRowId={setEditingRowId}
180205
setActiveActionMenuRowId={setActiveActionMenuRowId}
181206
hasOpenDraft={hasOpenDraft}
@@ -192,13 +217,14 @@ function getColumns({
192217

193218
const disableAddSubtag = hasOpenDraft || !canAddTag;
194219
const disableEditTag = hasOpenDraft || rowData.canChangeTag === false;
220+
const disableDeleteTag = hasOpenDraft || rowData.canDeleteTag === false;
195221

196222
const startSubtagDraft = () => {
197223
onStartDraft();
198224
setDraftError('');
199225
setCreatingParentId(rowData.id);
200226
setEditingRowId(null);
201-
setIsCreatingTopTag(false);
227+
setIsCreatingTopRow(false);
202228
setActiveActionMenuRowId(null);
203229
row.toggleExpanded(true);
204230
};
@@ -208,10 +234,14 @@ function getColumns({
208234
setDraftError('');
209235
setEditingRowId(`${rowData.id}:${rowData.value}`);
210236
setCreatingParentId(null);
211-
setIsCreatingTopTag(false);
237+
setIsCreatingTopRow(false);
212238
setActiveActionMenuRowId(null);
213239
};
214240

241+
const deleteTag = () => {
242+
// todo: implement
243+
};
244+
215245
return (
216246
<div className="d-flex align-items-center justify-content-end gap-2">
217247
<ActionsMenu
@@ -222,6 +252,8 @@ function getColumns({
222252
editTag={editTag}
223253
disableEditTag={disableEditTag}
224254
reachedMaxDepth={reachedMaxDepth}
255+
deleteTag={deleteTag}
256+
disableDeleteTag={disableDeleteTag}
225257
/>
226258
</div>
227259
);

0 commit comments

Comments
 (0)