diff --git a/src/course-unit/data/api.js b/src/course-unit/data/api.js index 7a4697406..58aa28426 100644 --- a/src/course-unit/data/api.js +++ b/src/course-unit/data/api.js @@ -3,7 +3,7 @@ import { camelCaseObject, getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { PUBLISH_TYPES } from '../constants'; -import { normalizeCourseSectionVerticalData, updateXBlockBlockIdToId } from './utils'; +import { isUnitReadOnly, normalizeCourseSectionVerticalData, updateXBlockBlockIdToId } from './utils'; const getStudioBaseUrl = () => getConfig().STUDIO_BASE_URL; @@ -24,7 +24,9 @@ export async function getCourseUnitData(unitId) { const { data } = await getAuthenticatedHttpClient() .get(getCourseUnitApiUrl(unitId)); - return camelCaseObject(data); + const result = camelCaseObject(data); + result.readOnly = isUnitReadOnly(result); + return result; } /** diff --git a/src/course-unit/data/thunk.js b/src/course-unit/data/thunk.js index ee2c4da65..481b9c6ca 100644 --- a/src/course-unit/data/thunk.js +++ b/src/course-unit/data/thunk.js @@ -38,7 +38,7 @@ import { updateCourseOutlineInfoLoadingStatus, updateMovedXBlockParams, } from './slice'; -import { getNotificationMessage, isUnitReadOnly } from './utils'; +import { getNotificationMessage } from './utils'; export function fetchCourseUnitQuery(courseId) { return async (dispatch) => { @@ -46,7 +46,6 @@ export function fetchCourseUnitQuery(courseId) { try { const courseUnit = await getCourseUnitData(courseId); - courseUnit.readOnly = isUnitReadOnly(courseUnit); dispatch(fetchCourseItemSuccess(courseUnit)); dispatch(updateLoadingCourseUnitStatus({ status: RequestStatus.SUCCESSFUL })); diff --git a/src/library-authoring/components/ContainerCard.tsx b/src/library-authoring/components/ContainerCard.tsx index 1617c9d1f..a6442b1b5 100644 --- a/src/library-authoring/components/ContainerCard.tsx +++ b/src/library-authoring/components/ContainerCard.tsx @@ -208,7 +208,9 @@ const ContainerCard = ({ hit } : ContainerCardProps) => { if (itemType === 'unit') { openUnitInfoSidebar(unitId); setUnitId(unitId); - navigateTo({ unitId }); + if (!componentPickerMode) { + navigateTo({ unitId }); + } } }, [unitId, itemType, openUnitInfoSidebar, navigateTo]); diff --git a/src/library-authoring/containers/UnitInfo.test.tsx b/src/library-authoring/containers/UnitInfo.test.tsx index 677063f85..9b154d13c 100644 --- a/src/library-authoring/containers/UnitInfo.test.tsx +++ b/src/library-authoring/containers/UnitInfo.test.tsx @@ -5,7 +5,7 @@ import { initializeMocks, render as baseRender, screen, waitFor, fireEvent, } from '../../testUtils'; -import { mockContentLibrary, mockGetContainerMetadata } from '../data/api.mocks'; +import { mockContentLibrary, mockGetContainerChildren, mockGetContainerMetadata } from '../data/api.mocks'; import { LibraryProvider } from '../common/context/LibraryContext'; import UnitInfo from './UnitInfo'; import { getLibraryContainerApiUrl, getLibraryContainerPublishApiUrl } from '../data/api'; @@ -14,14 +14,16 @@ import { SidebarBodyComponentId, SidebarProvider } from '../common/context/Sideb mockGetContainerMetadata.applyMock(); mockContentLibrary.applyMock(); mockGetContainerMetadata.applyMock(); +mockGetContainerChildren.applyMock(); const { libraryId } = mockContentLibrary; const { containerId } = mockGetContainerMetadata; -const render = () => baseRender(, { +const render = (showOnlyPublished: boolean = false) => baseRender(, { extraWrapper: ({ children }) => ( ', () => { }); expect(mockShowToast).toHaveBeenCalledWith('Failed to publish changes'); }); + + it('show only published content', async () => { + render(true); + expect(await screen.findByTestId('unit-info-menu-toggle')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /text block published 1/i })).toBeInTheDocument(); + }); }); diff --git a/src/library-authoring/data/api.mocks.ts b/src/library-authoring/data/api.mocks.ts index eb5f60188..6be3d681e 100644 --- a/src/library-authoring/data/api.mocks.ts +++ b/src/library-authoring/data/api.mocks.ts @@ -188,6 +188,7 @@ mockCreateLibraryBlock.newHtmlData = { id: 'lb:Axim:TEST:html:123', blockType: 'html', displayName: 'New Text Component', + publishedDisplayName: null, hasUnpublishedChanges: true, lastPublished: null, // or e.g. '2024-08-30T16:37:42Z', publishedBy: null, // or e.g. 'test_author', @@ -202,6 +203,7 @@ mockCreateLibraryBlock.newProblemData = { id: 'lb:Axim:TEST:problem:prob1', blockType: 'problem', displayName: 'New Problem', + publishedDisplayName: null, hasUnpublishedChanges: true, lastPublished: null, // or e.g. '2024-08-30T16:37:42Z', publishedBy: null, // or e.g. 'test_author', @@ -216,6 +218,7 @@ mockCreateLibraryBlock.newVideoData = { id: 'lb:Axim:TEST:video:vid1', blockType: 'video', displayName: 'New Video', + publishedDisplayName: null, hasUnpublishedChanges: true, lastPublished: null, // or e.g. '2024-08-30T16:37:42Z', publishedBy: null, // or e.g. 'test_author', @@ -348,6 +351,7 @@ mockLibraryBlockMetadata.dataNeverPublished = { id: 'lb:Axim:TEST1:html:571fe018-f3ce-45c9-8f53-5dafcb422fd1', blockType: 'html', displayName: 'Introduction to Testing 1', + publishedDisplayName: null, lastPublished: null, publishedBy: null, lastDraftCreated: null, @@ -363,6 +367,7 @@ mockLibraryBlockMetadata.dataPublished = { id: 'lb:Axim:TEST2:html:571fe018-f3ce-45c9-8f53-5dafcb422fd2', blockType: 'html', displayName: 'Introduction to Testing 2', + publishedDisplayName: 'Introduction to Testing 2', lastPublished: '2024-06-22T00:00:00', publishedBy: 'Luke', lastDraftCreated: null, @@ -391,6 +396,7 @@ mockLibraryBlockMetadata.dataWithCollections = { id: 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd', blockType: 'html', displayName: 'Introduction to Testing 2', + publishedDisplayName: null, lastPublished: '2024-06-21T00:00:00', publishedBy: 'Luke', lastDraftCreated: null, @@ -407,6 +413,7 @@ mockLibraryBlockMetadata.dataPublishedWithChanges = { id: 'lb:Axim:TEST2:html:571fe018-f3ce-45c9-8f53-5dafcb422fvv', blockType: 'html', displayName: 'Introduction to Testing 2', + publishedDisplayName: 'Introduction to Testing 3', lastPublished: '2024-06-22T00:00:00', publishedBy: 'Luke', lastDraftCreated: null, @@ -536,6 +543,7 @@ export async function mockGetContainerChildren(containerId: string): Promise `${getL /** * Get the URL for a single container children api. */ -export const getLibraryContainerChildrenApiUrl = (containerId: string) => `${getLibraryContainerApiUrl(containerId)}children/`; +export const getLibraryContainerChildrenApiUrl = (containerId: string, published: boolean = false) => `${getLibraryContainerApiUrl(containerId)}children/?published=${published}`; /** * Get the URL for library container collections. */ @@ -250,6 +250,7 @@ export interface LibraryBlockMetadata { id: string; blockType: string; displayName: string; + publishedDisplayName: string | null; lastPublished: string | null; publishedBy: string | null; lastDraftCreated: string | null; @@ -652,8 +653,13 @@ export async function restoreContainer(containerId: string) { /** * Fetch a library container's children's metadata. */ -export async function getLibraryContainerChildren(containerId: string): Promise { - const { data } = await getAuthenticatedHttpClient().get(getLibraryContainerChildrenApiUrl(containerId)); +export async function getLibraryContainerChildren( + containerId: string, + published: boolean = false, +): Promise { + const { data } = await getAuthenticatedHttpClient().get( + getLibraryContainerChildrenApiUrl(containerId, published), + ); return camelCaseObject(data); } diff --git a/src/library-authoring/data/apiHooks.ts b/src/library-authoring/data/apiHooks.ts index a74fc5097..01dd97c3e 100644 --- a/src/library-authoring/data/apiHooks.ts +++ b/src/library-authoring/data/apiHooks.ts @@ -641,11 +641,11 @@ export const useRestoreContainer = (containerId: string) => { /** * Get the metadata and children for a container in a library */ -export const useContainerChildren = (containerId?: string) => ( +export const useContainerChildren = (containerId?: string, published: boolean = false) => ( useQuery({ enabled: !!containerId, queryKey: libraryAuthoringQueryKeys.containerChildren(containerId!), - queryFn: () => api.getLibraryContainerChildren(containerId!), + queryFn: () => api.getLibraryContainerChildren(containerId!, published), structuralSharing: (oldData: api.LibraryBlockMetadata[], newData: api.LibraryBlockMetadata[]) => { // This just sets `isNew` flag to new children components if (oldData) { diff --git a/src/library-authoring/units/LibraryUnitBlocks.tsx b/src/library-authoring/units/LibraryUnitBlocks.tsx index f518e976d..4d42bf2a4 100644 --- a/src/library-authoring/units/LibraryUnitBlocks.tsx +++ b/src/library-authoring/units/LibraryUnitBlocks.tsx @@ -56,6 +56,7 @@ interface ComponentBlockProps { /** Component header */ const BlockHeader = ({ block }: ComponentBlockProps) => { const intl = useIntl(); + const { showOnlyPublished } = useLibraryContext(); const { showToast } = useContext(ToastContext); const { navigateTo } = useLibraryRoutes(); const { openComponentInfoSidebar, setSidebarAction } = useSidebarContext(); @@ -101,7 +102,7 @@ const BlockHeader = ({ block }: ComponentBlockProps) => { @@ -112,7 +113,7 @@ const BlockHeader = ({ block }: ComponentBlockProps) => { /* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */ onClick={(e) => e.stopPropagation()} > - {block.hasUnpublishedChanges && ( + {!showOnlyPublished && block.hasUnpublishedChanges && ( { const [hidePreviewFor, setHidePreviewFor] = useState(null); const { showToast } = useContext(ToastContext); - const { unitId, readOnly } = useLibraryContext(); + const { readOnly, showOnlyPublished } = useLibraryContext(); + const { sidebarComponentInfo } = useSidebarContext(); + const unitId = sidebarComponentInfo?.id; const { openAddContentSidebar } = useSidebarContext(); @@ -247,7 +250,7 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => { isLoading, isError, error, - } = useContainerChildren(unitId); + } = useContainerChildren(unitId, showOnlyPublished); const handleReorder = useCallback(() => async (newOrder?: LibraryBlockMetadataWithUniqueId[]) => { if (!newOrder) {