diff --git a/src/library-authoring/LibraryAuthoringPage.test.tsx b/src/library-authoring/LibraryAuthoringPage.test.tsx index 9eaf0167d..d6e7f1c30 100644 --- a/src/library-authoring/LibraryAuthoringPage.test.tsx +++ b/src/library-authoring/LibraryAuthoringPage.test.tsx @@ -702,6 +702,34 @@ describe('', () => { }); }); + it('Disables Type filter on Collections tab', async () => { + await renderLibraryPage(); + + expect(await screen.findByText('Content library')).toBeInTheDocument(); + expect((await screen.findAllByText(libraryTitle))[0]).toBeInTheDocument(); + expect((await screen.findAllByText('Introduction to Testing'))[0]).toBeInTheDocument(); + expect((await screen.findAllByText('Collection 1'))[0]).toBeInTheDocument(); + + // Filter by Text block type + fireEvent.click(screen.getByRole('button', { name: /type/i })); + fireEvent.click(screen.getByRole('checkbox', { name: /text/i })); + // Escape to close the Types filter drop-down and re-enable the tabs + fireEvent.keyDown(screen.getByRole('button', { name: /type/i }), { key: 'Escape' }); + + // Navigate to the collections tab + fireEvent.click(await screen.findByRole('tab', { name: 'Collections' })); + expect((await screen.findAllByText('Collection 1'))[0]).toBeInTheDocument(); + // No Types filter shown + expect(screen.queryByRole('button', { name: /type/i })).not.toBeInTheDocument(); + + // Navigate to the components tab + fireEvent.click(screen.getByRole('tab', { name: 'Components' })); + // Text components should be shown + expect((await screen.findAllByText('Introduction to Testing'))[0]).toBeInTheDocument(); + // Types filter is shown + expect(screen.getByRole('button', { name: /type/i })).toBeInTheDocument(); + }); + it('Shows an error if libraries V2 is disabled', async () => { const { axiosMock } = initializeMocks(); axiosMock.onGet(getStudioHomeApiUrl()).reply(200, { diff --git a/src/library-authoring/LibraryAuthoringPage.tsx b/src/library-authoring/LibraryAuthoringPage.tsx index 13687987e..b952310f5 100644 --- a/src/library-authoring/LibraryAuthoringPage.tsx +++ b/src/library-authoring/LibraryAuthoringPage.tsx @@ -30,6 +30,7 @@ import { SearchContextProvider, SearchKeywordsField, SearchSortWidget, + TypesFilterData, } from '../search-manager'; import LibraryContent from './LibraryContent'; import { LibrarySidebar } from './library-sidebar'; @@ -220,6 +221,9 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage extraFilter.push(activeTypeFilters[activeKey]); } + // Disable filtering by block/problem type when viewing the Collections tab. + const overrideTypesFilter = insideCollections ? new TypesFilterData() : undefined; + return (
@@ -239,6 +243,7 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage } @@ -260,7 +265,7 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage - + {!insideCollections && } diff --git a/src/search-manager/FilterByBlockType.tsx b/src/search-manager/FilterByBlockType.tsx index 16c0b1de1..0b5160d37 100644 --- a/src/search-manager/FilterByBlockType.tsx +++ b/src/search-manager/FilterByBlockType.tsx @@ -191,16 +191,12 @@ const FilterItem = ({ blockType, count } : FilterItemProps) => { ); }; -interface FilterByBlockTypeProps { - disabled?: boolean, -} /** * A button with a dropdown that allows filtering the current search by component type (XBlock type) * e.g. Limit results to "Text" (html) and "Problem" (problem) components. * The button displays the first type selected, and a count of how many other types are selected, if more than one. - * @param disabled - If true, the filter is disabled and hidden. */ -const FilterByBlockType: React.FC = ({ disabled = false }) => { +const FilterByBlockType: React.FC> = () => { const { blockTypes, typesFilter, @@ -248,10 +244,6 @@ const FilterByBlockType: React.FC = ({ disabled = false blockType => ({ label: }), ); - if (disabled) { - return null; - } - return ( (undefined); export const SearchContextProvider: React.FC<{ - extraFilter?: Filter; + extraFilter?: Filter, + overrideTypesFilter?: TypesFilterData, overrideSearchSortOrder?: SearchSortOption children: React.ReactNode, closeSearchModal?: () => void, skipBlockTypeFetch?: boolean, skipUrlUpdate?: boolean, }> = ({ - overrideSearchSortOrder, skipBlockTypeFetch, skipUrlUpdate, ...props + overrideTypesFilter, + overrideSearchSortOrder, + skipBlockTypeFetch, + skipUrlUpdate, + ...props }) => { // Search parameters can be set via the query string // E.g. ?q=draft+text @@ -69,13 +74,15 @@ export const SearchContextProvider: React.FC<{ // Block + problem types use alphanumeric plus a few other characters. // E.g ?type=html&type=video&type=p.multiplechoiceresponse - const [typesFilter, setTypesFilter] = useStateOrUrlSearchParam( + const [internalTypesFilter, setTypesFilter] = useStateOrUrlSearchParam( new TypesFilterData(), 'type', (value: string | null) => new TypesFilterData(value), (value: TypesFilterData | undefined) => (value ? value.toString() : undefined), skipUrlUpdate, ); + // Callers can override the types filter when searching, but we still preserve the user's selected state. + const typesFilter = overrideTypesFilter ?? internalTypesFilter; // Tags can be almost any string value (see openedx-learning's RESERVED_TAG_CHARS) // and multiple tags may be selected together. diff --git a/src/search-manager/index.ts b/src/search-manager/index.ts index e2d4188be..278a53715 100644 --- a/src/search-manager/index.ts +++ b/src/search-manager/index.ts @@ -9,5 +9,6 @@ export { default as SearchSortWidget } from './SearchSortWidget'; export { default as Stats } from './Stats'; export { HIGHLIGHT_PRE_TAG, HIGHLIGHT_POST_TAG } from './data/api'; export { useGetBlockTypes } from './data/apiHooks'; +export { TypesFilterData } from './hooks'; export type { CollectionHit, ContentHit, ContentHitTags } from './data/api';