Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/taxonomy/data/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export const apiUrls = {
/** URL to plan (preview what would happen) a taxonomy import */
tagsPlanImport: (taxonomyId: number) => makeUrl(`${taxonomyId}/tags/import/plan/`),
createTag: (taxonomyId: number) => makeUrl(`${taxonomyId}/tags/`),
updateTag: (taxonomyId: number) => makeUrl(`${taxonomyId}/tags/`),
} satisfies Record<string, (...args: any[]) => string>;

/**
Expand Down
24 changes: 24 additions & 0 deletions src/taxonomy/data/apiHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,27 @@ export const useCreateTag = (taxonomyId: number) => {
},
});
};

export const useUpdateTag = (taxonomyId: number) => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: async ({ value, originalValue }: { value: string, originalValue: string }) => {
try {
await getAuthenticatedHttpClient().patch(
apiUrls.updateTag(taxonomyId),
{ tag: originalValue, updated_tag_value: value },
);
} catch (err) {
throw new Error(getApiErrorMessage(err));
}
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: taxonomyQueryKeys.taxonomyTagList(taxonomyId),
});
// In the metadata, 'tagsCount' (and possibly other fields) will have changed:
queryClient.invalidateQueries({ queryKey: taxonomyQueryKeys.taxonomyMetadata(taxonomyId) });
},
});
};
1 change: 1 addition & 0 deletions src/taxonomy/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface TagListData {
next: string;
numPages: number;
previous: string;
canAddTag?: boolean;
results: TagData[];
start: number;
}
517 changes: 356 additions & 161 deletions src/taxonomy/tag-list/TagListTable.test.jsx

Large diffs are not rendered by default.

12 changes: 9 additions & 3 deletions src/taxonomy/tag-list/TagListTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React, {
useEffect,
} from 'react';
import type { PaginationState } from '@tanstack/react-table';
import { useTagListData, useCreateTag } from '../data/apiHooks';
import { useTagListData, useCreateTag, useUpdateTag } from '../data/apiHooks';
import { TagTree } from './tagTree';
import { TableView } from '../tree-table';
import type {
Expand Down Expand Up @@ -78,6 +78,7 @@ const TagListTable = ({ taxonomyId, maxDepth }: TagListTableProps) => {
enabled: tableMode === TABLE_MODES.VIEW,
});
const createTagMutation = useCreateTag(taxonomyId);
const updateTagMutation = useUpdateTag(taxonomyId);
const pageCount = tagList?.numPages ?? -1;

// TODO: to make this more readable, introduce a React context for the TagListTable instead of passing props.
Expand All @@ -88,6 +89,7 @@ const TagListTable = ({ taxonomyId, maxDepth }: TagListTableProps) => {
setTagTree,
setDraftError,
createTagMutation,
updateTagMutation,
enterPreviewMode,
setToast,
setIsCreatingTopTag,
Expand All @@ -105,19 +107,19 @@ const TagListTable = ({ taxonomyId, maxDepth }: TagListTableProps) => {
onStartDraft: enterDraftMode,
setActiveActionMenuRowId,
hasOpenDraft,
canAddTag: tagList?.canAddTag !== false,
draftError,
setDraftError,
isSavingDraft: createTagMutation.isPending,
maxDepth,
creatingParentId,
}),
[
isCreatingTopTag,
editingRowId,
tableMode,
activeActionMenuRowId,
hasOpenDraft,
creatingParentId,
tagList?.canAddTag,
draftError,
createTagMutation.isPending,
maxDepth,
Expand Down Expand Up @@ -155,7 +157,9 @@ const TagListTable = ({ taxonomyId, maxDepth }: TagListTableProps) => {
isCreatingTopRow: isCreatingTopTag,
draftError,
createRowMutation: createTagMutation,
updateRowMutation: updateTagMutation,
handleCreateRow: handleCreateTag,
handleUpdateRow: handleUpdateTag,
toast,
setToast,
setIsCreatingTopRow: setIsCreatingTopTag,
Expand All @@ -164,6 +168,8 @@ const TagListTable = ({ taxonomyId, maxDepth }: TagListTableProps) => {
setCreatingParentId,
setDraftError,
validate,
editingRowId,
setEditingRowId,
}}
/>
);
Expand Down
9 changes: 7 additions & 2 deletions src/taxonomy/tag-list/hooks.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { act, renderHook } from '@testing-library/react';
import { act, renderHook, waitFor } from '@testing-library/react';

import { TagTree } from './tagTree';
import { useEditActions, useTableModes } from './hooks';
Expand Down Expand Up @@ -44,6 +44,8 @@ describe('useTableModes', () => {
describe('useEditActions', () => {
const buildActions = (overrides = {}) => {
const createTagMutation = { mutateAsync: jest.fn() };
// mock updateTagMutation to have a function `mutateAsync` that returns a resolved promise
const updateTagMutation = { mutateAsync: jest.fn() };
const setTagTree = jest.fn();
const setDraftError = jest.fn();
const enterPreviewMode = jest.fn();
Expand All @@ -63,6 +65,7 @@ describe('useEditActions', () => {
setCreatingParentId,
exitDraftWithoutSave,
setEditingRowId,
updateTagMutation: updateTagMutation as any,
...(overrides as any),
};

Expand Down Expand Up @@ -139,7 +142,9 @@ describe('useEditActions', () => {
await actions.handleUpdateTag('updated', 'original');
});

expect(enterPreviewMode).toHaveBeenCalled();
await waitFor(() => {
expect(enterPreviewMode).toHaveBeenCalled();
});
expect(setToast).toHaveBeenCalledWith({
show: true,
message: 'Tag "updated" updated successfully',
Expand Down
35 changes: 32 additions & 3 deletions src/taxonomy/tag-list/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useReducer } from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';

import { useCreateTag } from '../data/apiHooks';
import { useCreateTag, useUpdateTag } from '../data/apiHooks';
import { TagTree } from './tagTree';
import { TagListTableError } from './errors';
import type { RowId } from '../tree-table/types';
Expand Down Expand Up @@ -45,6 +45,7 @@ interface UseEditActionsParams {
setCreatingParentId: React.Dispatch<React.SetStateAction<RowId | null>>;
exitDraftWithoutSave: () => void;
setEditingRowId: React.Dispatch<React.SetStateAction<RowId | null>>;
updateTagMutation: ReturnType<typeof useUpdateTag>;
}

const getInlineValidationMessage = (value: string, intl: ReturnType<typeof useIntl>): string => {
Expand Down Expand Up @@ -111,9 +112,20 @@ const useEditActions = ({
setToast,
setIsCreatingTopTag,
setCreatingParentId,
exitDraftWithoutSave,
setEditingRowId,
updateTagMutation,
}: UseEditActionsParams) => {
const intl = useIntl();

const updateTableAfterRename = (oldValue: string, newValue: string) => {
setTagTree((currentTagTree) => {
const nextTree = currentTagTree || new TagTree([]);
nextTree.editTagValue(oldValue, newValue);
return nextTree;
});
};

const updateTableWithoutDataReload = (value: string, parentTagValue: string | null = null) => {
setTagTree((currentTagTree) => {
const nextTree = currentTagTree || new TagTree([]);
Expand Down Expand Up @@ -179,14 +191,31 @@ const useEditActions = ({

const handleUpdateTag = async (value: string, originalValue: string) => {
const trimmed = value.trim();
if (trimmed && trimmed !== originalValue) {
if (!validate(trimmed, 'soft')) {
return;
}

if (trimmed === originalValue) {
setEditingRowId(null);
exitDraftWithoutSave();
return;
}

try {
setDraftError('');
await updateTagMutation.mutateAsync({ value: trimmed, originalValue });
updateTableAfterRename(originalValue, trimmed);
enterPreviewMode();
setEditingRowId(null);
setToast({
show: true,
message: intl.formatMessage(messages.tagUpdateSuccessMessage, { name: trimmed }),
});
} catch (error) {
const message = intl.formatMessage(messages.tagUpdateErrorMessage, { errorMessage: (error as Error)?.message });
setDraftError((error as Error)?.message || intl.formatMessage(messages.tagUpdateErrorMessage, { errorMessage: '' }));
setToast({ show: true, message });
}
setEditingRowId(null);
};

return {
Expand Down
8 changes: 8 additions & 0 deletions src/taxonomy/tag-list/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ const messages = defineMessages({
id: 'course-authoring.tag-list.hide-subtags.button-label',
defaultMessage: 'Hide Subtags',
},
tagUpdateErrorMessage: {
id: 'course-authoring.tag-list.update-error',
defaultMessage: 'Error updating tag: {errorMessage}',
},
renameTag: {
id: 'course-authoring.tag-list.rename-tag',
defaultMessage: 'Rename',
},
});

export default messages;
Loading