diff --git a/src/course-outline/CourseOutline.test.tsx b/src/course-outline/CourseOutline.test.tsx
index bc97222ff7..80d161ae81 100644
--- a/src/course-outline/CourseOutline.test.tsx
+++ b/src/course-outline/CourseOutline.test.tsx
@@ -708,7 +708,7 @@ describe('', () => {
const { findByLabelText } = renderComponent();
axiosMock
- .onPost(getCourseBlockApiUrl(courseId), {
+ .onPatch(getCourseBlockApiUrl(courseId), {
metadata: {
video_sharing_options: VIDEO_SHARING_OPTIONS.allOff,
},
@@ -721,7 +721,7 @@ describe('', () => {
// The video sharing POST is the one with the expected data.
// Find it by data content rather than assuming a fixed index.
- const videoSharingPost = axiosMock.history.post.find(
+ const videoSharingPost = axiosMock.history.patch.find(
(entry: any) => entry.data && entry.data.includes('video_sharing_options'),
);
expect(videoSharingPost).toBeDefined();
@@ -738,7 +738,7 @@ describe('', () => {
renderComponent();
axiosMock
- .onPost(getCourseBlockApiUrl(courseId), {
+ .onPatch(getCourseBlockApiUrl(courseId), {
metadata: {
video_sharing_options: VIDEO_SHARING_OPTIONS.allOff,
},
@@ -749,7 +749,7 @@ describe('', () => {
async () => fireEvent.change(optionDropdown, { target: { value: VIDEO_SHARING_OPTIONS.allOff } }),
);
- const videoSharingPost = axiosMock.history.post.find(
+ const videoSharingPost = axiosMock.history.patch.find(
(entry: any) => entry.data && entry.data.includes('video_sharing_options'),
);
expect(videoSharingPost).toBeDefined();
@@ -955,7 +955,6 @@ describe('', () => {
);
expect(newUnitPost).toBeDefined();
expect(JSON.parse(newUnitPost!.data)).toEqual({
- type: COURSE_BLOCK_NAMES.vertical.id,
category: COURSE_BLOCK_NAMES.vertical.id,
parent_locator: subsection.id,
display_name: COURSE_BLOCK_NAMES.vertical.name,
@@ -1065,7 +1064,7 @@ describe('', () => {
axiosMock.reset();
axiosMock
- .onPost(getCourseBlockApiUrl(courseId), {
+ .onPatch(getCourseBlockApiUrl(courseId), {
publish: 'republish',
metadata: {
highlights_enabled_for_messaging: true,
@@ -1178,7 +1177,7 @@ describe('', () => {
.reply(200, courseLaunchMock);
// Rename-specific handlers
axiosMock
- .onPost(getCourseItemApiUrl(item.id))
+ .onPatch(getCourseItemApiUrl(item.id))
.reply(200, { dummy: 'value' });
// mock section, subsection and unit name and check within the elements.
@@ -1213,7 +1212,7 @@ describe('', () => {
fireEvent.change(editField, { target: { value: newName } });
await user.keyboard('{enter}');
expect(
- axiosMock.history.post[axiosMock.history.post.length - 1].data,
+ axiosMock.history.patch[axiosMock.history.patch.length - 1].data,
).toBe(JSON.stringify({
metadata: {
display_name: newName,
@@ -1432,7 +1431,7 @@ describe('', () => {
).toHaveTextContent(cardHeaderMessages.statusBadgeDraft.defaultMessage);
axiosMock
- .onPost(getCourseItemApiUrl(item.id), {
+ .onPatch(getCourseItemApiUrl(item.id), {
publish: 'make_public',
})
.reply(200, { dummy: 'value' });
@@ -1500,7 +1499,7 @@ describe('', () => {
expect(releaseDatePicker).toHaveValue('08/10/2023');
axiosMock
- .onPost(getCourseItemApiUrl(section.id), {
+ .onPatch(getCourseItemApiUrl(section.id), {
publish: 'republish',
metadata: {
visible_to_staff_only: true,
@@ -1521,7 +1520,7 @@ describe('', () => {
const saveButton = await findByTestId('configure-save-button');
await act(async () => fireEvent.click(saveButton));
- const sectionCfgPost = axiosMock.history.post.find(
+ const sectionCfgPost = axiosMock.history.patch.find(
(entry: any) => entry.data && entry.data.includes('visible_to_staff_only'),
);
expect(sectionCfgPost).toBeDefined();
@@ -1571,7 +1570,7 @@ describe('', () => {
};
axiosMock
- .onPost(getCourseItemApiUrl(subsection.id), expectedRequestData)
+ .onPatch(getCourseItemApiUrl(subsection.id), expectedRequestData)
.reply(200, { dummy: 'value' });
const [currentSection] = await findAllByTestId('section-card');
@@ -1630,7 +1629,7 @@ describe('', () => {
await user.click(saveButton);
// verify request
- const subCfgPost = axiosMock.history.post.find(
+ const subCfgPost = axiosMock.history.patch.find(
(entry: any) => entry.data && entry.data.includes(expectedRequestData.graderType),
);
expect(subCfgPost).toBeDefined();
@@ -1698,7 +1697,7 @@ describe('', () => {
};
axiosMock
- .onPost(getCourseItemApiUrl(subsection.id), expectedRequestData)
+ .onPatch(getCourseItemApiUrl(subsection.id), expectedRequestData)
.reply(200, { dummy: 'value' });
const [currentSection] = await findAllByTestId('section-card');
@@ -1775,7 +1774,7 @@ describe('', () => {
await user.click(saveButton);
// verify request
- const cfgPosta = axiosMock.history.post.find(
+ const cfgPosta = axiosMock.history.patch.find(
(entry: any) => entry.data && entry.data.includes(expectedRequestData.graderType),
);
expect(cfgPosta).toBeDefined();
@@ -1849,7 +1848,7 @@ describe('', () => {
};
axiosMock
- .onPost(getCourseItemApiUrl(subsection.id), expectedRequestData)
+ .onPatch(getCourseItemApiUrl(subsection.id), expectedRequestData)
.reply(200, { dummy: 'value' });
const [currentSection] = await findAllByTestId('section-card');
@@ -1904,7 +1903,7 @@ describe('', () => {
await user.click(saveButton);
// verify request
- const cfgPostb = axiosMock.history.post.find(
+ const cfgPostb = axiosMock.history.patch.find(
(entry: any) => entry.data && entry.data.includes(expectedRequestData.graderType),
);
expect(cfgPostb).toBeDefined();
@@ -1960,7 +1959,7 @@ describe('', () => {
};
axiosMock
- .onPost(getCourseItemApiUrl(subsection.id), expectedRequestData)
+ .onPatch(getCourseItemApiUrl(subsection.id), expectedRequestData)
.reply(200, { dummy: 'value' });
const [currentSection] = await findAllByTestId('section-card');
@@ -2016,7 +2015,7 @@ describe('', () => {
await user.click(saveButton);
// verify request
- const cfgPostc = axiosMock.history.post.find(
+ const cfgPostc = axiosMock.history.patch.find(
(entry: any) => entry.data && entry.data.includes(expectedRequestData.graderType),
);
expect(cfgPostc).toBeDefined();
@@ -2071,7 +2070,7 @@ describe('', () => {
};
axiosMock
- .onPost(getCourseItemApiUrl(subsection.id), expectedRequestData)
+ .onPatch(getCourseItemApiUrl(subsection.id), expectedRequestData)
.reply(200, { dummy: 'value' });
const [, currentSection] = await findAllByTestId('section-card');
@@ -2125,7 +2124,7 @@ describe('', () => {
await user.click(saveButton);
// verify request
- const noSpecialPost = axiosMock.history.post.find(
+ const noSpecialPost = axiosMock.history.patch.find(
(entry: any) => entry.data && entry.data.includes(expectedRequestData.graderType),
);
expect(noSpecialPost).toBeDefined();
@@ -2169,7 +2168,7 @@ describe('', () => {
const isVisibleToStaffOnly = true;
axiosMock
- .onPost(getCourseItemApiUrl(unit.id), {
+ .onPatch(getCourseItemApiUrl(unit.id), {
publish: 'republish',
metadata: {
visible_to_staff_only: isVisibleToStaffOnly,
@@ -2297,7 +2296,7 @@ describe('', () => {
];
axiosMock
- .onPost(getCourseItemApiUrl(section.id), {
+ .onPatch(getCourseItemApiUrl(section.id), {
publish: 'republish',
metadata: {
highlights,
diff --git a/src/course-outline/data/api.ts b/src/course-outline/data/api.ts
index 4a95b4d19b..40ecacd0c5 100644
--- a/src/course-outline/data/api.ts
+++ b/src/course-outline/data/api.ts
@@ -46,13 +46,19 @@ export const getCourseLaunchApiUrl = ({
export const getCourseBlockApiUrl = (courseId: string) => {
const formattedCourseId = courseIDtoBlockID(courseId);
- return `${getApiBaseUrl()}/xblock/${formattedCourseId}`;
+ // FC-0118: v1 xblock REST detail URL for the course's root block.
+ return `${getApiBaseUrl()}/api/contentstore/v1/xblock/${formattedCourseId}/`;
};
export const getCourseReindexApiUrl = (reindexLink: string) => `${getApiBaseUrl()}${reindexLink}`;
-export const getXBlockBaseApiUrl = () => `${getApiBaseUrl()}/xblock/`;
-export const getCourseItemApiUrl = (itemId: string) => `${getXBlockBaseApiUrl()}${itemId}`;
-export const getXBlockApiUrl = (blockId: string) => `${getXBlockBaseApiUrl()}outline/${blockId}`;
+// FC-0118 (ADR 0028/0037): standardized v1 xblock REST endpoint. Collection URL
+// (POST here creates a block); detail URLs carry the trailing slash the DRF
+// DefaultRouter requires.
+export const getXBlockBaseApiUrl = () => `${getApiBaseUrl()}/api/contentstore/v1/xblock/`;
+export const getCourseItemApiUrl = (itemId: string) => `${getXBlockBaseApiUrl()}${itemId}/`;
+// The outline handler (`/xblock/outline/{id}`) is a separate legacy Studio route,
+// NOT part of the v1 REST viewset, so it keeps the legacy `/xblock/` base.
+export const getXBlockApiUrl = (blockId: string) => `${getApiBaseUrl()}/xblock/outline/${blockId}`;
export const exportTags = (courseId: string) =>
`${getApiBaseUrl()}/api/content_tagging/v1/object_tags/${courseId}/export/`;
export const createDiscussionsTopicsUrl = (courseId: string) =>
@@ -163,7 +169,7 @@ export async function getCourseLaunch({
*/
export async function enableCourseHighlightsEmails(courseId: string): Promise