Skip to content
Merged
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
38 changes: 36 additions & 2 deletions src/library-authoring/containers/UnitInfo.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ import {
import { mockContentLibrary, mockGetContainerMetadata } from '../data/api.mocks';
import { LibraryProvider } from '../common/context/LibraryContext';
import UnitInfo from './UnitInfo';
import { getLibraryContainerApiUrl } from '../data/api';
import { getLibraryContainerApiUrl, getLibraryContainerPublishApiUrl } from '../data/api';
import { SidebarBodyComponentId, SidebarProvider } from '../common/context/SidebarContext';

mockGetContainerMetadata.applyMock();
mockContentLibrary.applyMock();
mockGetContainerMetadata.applyMock();

const { libraryId } = mockContentLibrary;
const { containerId } = mockGetContainerMetadata;

const render = () => baseRender(<UnitInfo />, {
extraWrapper: ({ children }) => (
<LibraryProvider
Expand All @@ -38,7 +42,7 @@ describe('<UnitInfo />', () => {
({ axiosMock, mockShowToast } = initializeMocks());
});

it('should detele the unit using the menu', async () => {
it('should delete the unit using the menu', async () => {
axiosMock.onDelete(getLibraryContainerApiUrl(containerId)).reply(200);
render();

Expand All @@ -61,4 +65,34 @@ describe('<UnitInfo />', () => {
});
expect(mockShowToast).toHaveBeenCalled();
});

it('can publish the container', async () => {
axiosMock.onPost(getLibraryContainerPublishApiUrl(containerId)).reply(200);
render();

// Click on Publish button
const publishButton = await screen.findByRole('button', { name: 'Publish' });
expect(publishButton).toBeInTheDocument();
userEvent.click(publishButton);

await waitFor(() => {
expect(axiosMock.history.post.length).toBe(1);
});
expect(mockShowToast).toHaveBeenCalledWith('All changes published');
});

it('shows an error if publishing the container fails', async () => {
axiosMock.onPost(getLibraryContainerPublishApiUrl(containerId)).reply(500);
render();

// Click on Publish button
const publishButton = await screen.findByRole('button', { name: 'Publish' });
expect(publishButton).toBeInTheDocument();
userEvent.click(publishButton);

await waitFor(() => {
expect(axiosMock.history.post.length).toBe(1);
});
expect(mockShowToast).toHaveBeenCalledWith('Failed to publish changes');
});
});
38 changes: 31 additions & 7 deletions src/library-authoring/containers/UnitInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
IconButton,
useToggle,
} from '@openedx/paragon';
import { useEffect, useCallback } from 'react';
import React, { useEffect, useCallback } from 'react';
import { Link } from 'react-router-dom';
import { MoreVert } from '@openedx/paragon/icons';

Expand All @@ -28,7 +28,8 @@ import { LibraryUnitBlocks } from '../units/LibraryUnitBlocks';
import messages from './messages';
import componentMessages from '../components/messages';
import ContainerDeleter from '../components/ContainerDeleter';
import { useContainer } from '../data/apiHooks';
import { useContainer, usePublishContainer } from '../data/apiHooks';
import { ToastContext } from '../../generic/toast-context';

type ContainerMenuProps = {
containerId: string,
Expand Down Expand Up @@ -71,8 +72,9 @@ const UnitMenu = ({ containerId, displayName }: ContainerMenuProps) => {
const UnitInfo = () => {
const intl = useIntl();

const { libraryId } = useLibraryContext();
const { libraryId, readOnly } = useLibraryContext();
const { componentPickerMode } = useComponentPickerContext();
const { showToast } = React.useContext(ToastContext);
const {
defaultTab,
hiddenTabs,
Expand All @@ -90,6 +92,7 @@ const UnitInfo = () => {

const unitId = sidebarComponentInfo?.id;
const { data: container } = useContainer(unitId);
const publishContainer = usePublishContainer(unitId!);

const showOpenUnitButton = !insideUnit && !componentPickerMode;

Expand All @@ -105,6 +108,15 @@ const UnitInfo = () => {
);
}, [hiddenTabs, defaultTab.unit, unitId]);

const handlePublish = React.useCallback(async () => {
try {
await publishContainer.mutateAsync();
showToast(intl.formatMessage(messages.publishContainerSuccess));
} catch (error) {
showToast(intl.formatMessage(messages.publishContainerFailed));
}
}, [publishContainer]);

useEffect(() => {
// Show Organize tab if JumpToAddCollections action is set in sidebarComponentInfo
if (jumpToCollections) {
Expand All @@ -118,8 +130,8 @@ const UnitInfo = () => {

return (
<Stack>
{showOpenUnitButton && (
<div className="d-flex flex-wrap">
<div className="d-flex flex-wrap">
{showOpenUnitButton && (
<Button
variant="outline-primary"
className="m-1 text-nowrap flex-grow-1"
Expand All @@ -128,12 +140,24 @@ const UnitInfo = () => {
>
{intl.formatMessage(messages.openUnitButton)}
</Button>
)}
{!componentPickerMode && !readOnly && (
<Button
variant="outline-primary"
className="m-1 text-nowrap flex-grow-1"
disabled={!container.hasUnpublishedChanges || publishContainer.isLoading}
onClick={handlePublish}
>
{intl.formatMessage(messages.publishContainerButton)}
</Button>
)}
{showOpenUnitButton && ( // Check: should we still show this on the unit page?
<UnitMenu
containerId={unitId}
displayName={container.displayName}
/>
</div>
)}
)}
</div>
<Tabs
variant="tabs"
className="my-3 d-flex justify-content-around"
Expand Down
15 changes: 15 additions & 0 deletions src/library-authoring/containers/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ const messages = defineMessages({
defaultMessage: 'Collections ({count})',
description: 'Title for collections section in organize tab',
},
publishContainerButton: {
id: 'course-authoring.library-authoring.container-sidebar.publish-button',
defaultMessage: 'Publish',
description: 'Button text to publish the unit/subsection/section',
},
publishContainerSuccess: {
id: 'course-authoring.library-authoring.container-sidebar.publish-success',
defaultMessage: 'All changes published',
description: 'Popup text after publishing a unit/subsection/section',
},
publishContainerFailed: {
id: 'course-authoring.library-authoring.container-sidebar.publish-failure',
defaultMessage: 'Failed to publish changes',
description: 'Popup text seen if publishing a unit/subsection/section fails',
},
settingsTabTitle: {
id: 'course-authoring.library-authoring.container-sidebar.settings-tab.title',
defaultMessage: 'Settings',
Expand Down
14 changes: 14 additions & 0 deletions src/library-authoring/data/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ export const getLibraryContainerChildrenApiUrl = (containerId: string) => `${get
* Get the URL for library container collections.
*/
export const getLibraryContainerCollectionsUrl = (containerId: string) => `${getLibraryContainerApiUrl(containerId)}collections/`;
/**
* Get the URL for the API endpoint to publish a single container (+ children).
*/
export const getLibraryContainerPublishApiUrl = (containerId: string) => `${getLibraryContainerApiUrl(containerId)}publish/`;

export interface ContentLibrary {
id: string;
Expand Down Expand Up @@ -700,3 +704,13 @@ export async function removeLibraryContainerChildren(
);
return camelCaseObject(data);
}

/**
* Publish a container, and any unpublished children within it.
*
* This doesn't return any data at the moment, but we could have it return a
* list of the auto-published children in the future, if that would be helpful.
*/
export async function publishContainer(containerId: string) {
await getAuthenticatedHttpClient().post(getLibraryContainerPublishApiUrl(containerId));
}
14 changes: 14 additions & 0 deletions src/library-authoring/data/apiHooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getLibraryContainerApiUrl,
getLibraryContainerRestoreApiUrl,
getLibraryContainerChildrenApiUrl,
getLibraryContainerPublishApiUrl,
} from './api';
import {
useCommitLibraryChanges,
Expand All @@ -31,6 +32,7 @@ import {
useAddComponentsToContainer,
useUpdateContainerChildren,
useRemoveContainerChildren,
usePublishContainer,
} from './apiHooks';

let axiosMock;
Expand Down Expand Up @@ -308,4 +310,16 @@ describe('library api hooks', () => {
expect(axiosMock.history.patch.length).toEqual(0);
});
});

describe('publishContainer', () => {
it('should publish a container', async () => {
const containerId = 'lct:org:lib:unit:1';
const url = getLibraryContainerPublishApiUrl(containerId);
axiosMock.onPost(url).reply(200);
const { result } = renderHook(() => usePublishContainer(containerId), { wrapper });
await result.current.mutateAsync();

expect(axiosMock.history.post[0].url).toEqual(url);
});
});
});
Loading