From 88b54383ec4deb0eed1af37b05698193ab8e5c3d Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Wed, 21 Feb 2024 12:09:53 -0500 Subject: [PATCH 01/17] refactor: Unit sidebar to create the TagsSidebar --- src/course-unit/CourseUnit.jsx | 5 +- .../SequenceNavigationTabs.jsx | 2 +- .../sidebar/components/SidebarHeader.jsx | 2 - .../sidebar/components/TagsSidebarBody.jsx | 43 ++++++++++++ src/course-unit/sidebar/index.jsx | 70 ++++++++++++++----- src/course-unit/sidebar/messages.js | 8 +++ 6 files changed, 107 insertions(+), 23 deletions(-) create mode 100644 src/course-unit/sidebar/components/TagsSidebarBody.jsx diff --git a/src/course-unit/CourseUnit.jsx b/src/course-unit/CourseUnit.jsx index 8a117abab3..3a1b80ac25 100644 --- a/src/course-unit/CourseUnit.jsx +++ b/src/course-unit/CourseUnit.jsx @@ -123,8 +123,9 @@ const CourseUnit = ({ courseId }) => { - - + + + diff --git a/src/course-unit/course-sequence/sequence-navigation/SequenceNavigationTabs.jsx b/src/course-unit/course-sequence/sequence-navigation/SequenceNavigationTabs.jsx index 370488ce06..e0e79048dd 100644 --- a/src/course-unit/course-sequence/sequence-navigation/SequenceNavigationTabs.jsx +++ b/src/course-unit/course-sequence/sequence-navigation/SequenceNavigationTabs.jsx @@ -1,9 +1,9 @@ import { useDispatch, useSelector } from 'react-redux'; import PropTypes from 'prop-types'; +import { Link, useNavigate } from 'react-router-dom'; import { Button } from '@openedx/paragon'; import { Plus as PlusIcon } from '@openedx/paragon/icons'; import { useIntl } from '@edx/frontend-platform/i18n'; -import { useNavigate } from 'react-router-dom'; import { changeEditTitleFormOpen, updateQueryPendingStatus } from '../../data/slice'; import { getCourseId, getSequenceId } from '../../data/selectors'; diff --git a/src/course-unit/sidebar/components/SidebarHeader.jsx b/src/course-unit/sidebar/components/SidebarHeader.jsx index b6b6feda03..d94af33a46 100644 --- a/src/course-unit/sidebar/components/SidebarHeader.jsx +++ b/src/course-unit/sidebar/components/SidebarHeader.jsx @@ -1,11 +1,9 @@ import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import { Icon, Stack } from '@openedx/paragon'; -import { useIntl } from '@edx/frontend-platform/i18n'; import { getCourseUnitData } from '../../data/selectors'; import { getIconVariant } from '../utils'; -import messages from '../messages'; const SidebarHeader = ({ title, visibilityState, displayUnitLocation }) => { const intl = useIntl(); diff --git a/src/course-unit/sidebar/components/TagsSidebarBody.jsx b/src/course-unit/sidebar/components/TagsSidebarBody.jsx new file mode 100644 index 0000000000..a28ff53427 --- /dev/null +++ b/src/course-unit/sidebar/components/TagsSidebarBody.jsx @@ -0,0 +1,43 @@ +import { useState } from 'react'; +import { + Card, Stack, Button, Sheet, +} from '@openedx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { useParams } from 'react-router-dom'; +import { ContentTagsDrawer } from '../../../content-tags-drawer'; + +import messages from '../messages'; + +const TagsSidebarBody = () => { + const intl = useIntl(); + const [showManageTags, setShowManageTags] = useState(false); + const contentId = useParams().blockId; + const onClose = () => setShowManageTags(false); + + return ( + <> + + + Test + + + + + + + + ); +}; + +TagsSidebarBody.propTypes = {}; + +export default TagsSidebarBody; diff --git a/src/course-unit/sidebar/index.jsx b/src/course-unit/sidebar/index.jsx index f2817639b2..65b0f5b744 100644 --- a/src/course-unit/sidebar/index.jsx +++ b/src/course-unit/sidebar/index.jsx @@ -12,8 +12,9 @@ import { PUBLISH_TYPES } from '../constants'; import { SidebarBody, SidebarFooter, SidebarHeader } from './components'; import useCourseUnitData from './hooks'; import messages from './messages'; +import TagsSidebarBody from './components/TagsSidebarBody'; -const Sidebar = ({ blockId, displayUnitLocation, ...props }) => { +const Sidebar = ({ variant, blockId, ...props }) => { const { title, locationId, @@ -40,6 +41,41 @@ const Sidebar = ({ blockId, displayUnitLocation, ...props }) => { dispatch(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic)); }; + let sidebarTitle; + let sidebarBody; + let hideFooter = false; + let hideIcon = false; + switch (variant) { + case 'publish': + sidebarTitle = title; + sidebarBody = ( + + ); + break; + case 'location': + sidebarTitle = intl.formatMessage(messages.sidebarHeaderUnitLocationTitle); + sidebarBody = ( + + ); + break; + case 'tags': + sidebarTitle = intl.formatMessage(messages.tagsSidebarTitle); + sidebarBody = ( + + ); + hideFooter = true; + hideIcon = true; + break; + default: + break; + } + return ( { {...props} > - - + { sidebarBody } + { !hideFooter + && ( + + )} { Sidebar.propTypes = { blockId: PropTypes.string, - displayUnitLocation: PropTypes.bool, + variant: PropTypes.string.isRequired, }; Sidebar.defaultProps = { blockId: null, - displayUnitLocation: false, }; export default Sidebar; diff --git a/src/course-unit/sidebar/messages.js b/src/course-unit/sidebar/messages.js index 7d9d161d5c..872c506d53 100644 --- a/src/course-unit/sidebar/messages.js +++ b/src/course-unit/sidebar/messages.js @@ -137,6 +137,14 @@ const messages = defineMessages({ id: 'course-authoring.course-unit.modal.make-visibility.description', defaultMessage: 'If the unit was previously published and released to students, any changes you made to the unit when it was hidden will now be visible to students. Do you want to proceed?', }, + tagsSidebarTitle: { + id: 'course-authoring.course-unit.sidebar.tags.title', + defaultMessage: 'Unit Tags', + }, + tagsSidebarManageButtonLabel: { + id: 'course-authoring.course-unit.sidebar.tags.button.manage', + defaultMessage: 'Manage tags', + }, }); export default messages; From 683985943c8bc94d4ecf49ebf889cd1b9d8e4a82 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 22 Feb 2024 11:51:48 -0500 Subject: [PATCH 02/17] feat: Structure of TagsSidebar and TagsTree --- src/content-tags-drawer/index.scss | 2 + src/content-tags-drawer/messages.js | 4 + .../tags-sidebar/TagsSidebarBody.jsx | 102 ++++++++++++++++++ .../tags-sidebar/TagsTree.jsx | 34 ++++++ .../tags-sidebar/TagsTree.scss | 6 ++ src/content-tags-drawer/tags-sidebar/index.js | 2 + .../sidebar/components/TagsSidebarBody.jsx | 43 -------- src/course-unit/sidebar/index.jsx | 2 +- src/index.scss | 2 +- 9 files changed, 152 insertions(+), 45 deletions(-) create mode 100644 src/content-tags-drawer/index.scss create mode 100644 src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx create mode 100644 src/content-tags-drawer/tags-sidebar/TagsTree.jsx create mode 100644 src/content-tags-drawer/tags-sidebar/TagsTree.scss create mode 100644 src/content-tags-drawer/tags-sidebar/index.js delete mode 100644 src/course-unit/sidebar/components/TagsSidebarBody.jsx diff --git a/src/content-tags-drawer/index.scss b/src/content-tags-drawer/index.scss new file mode 100644 index 0000000000..9cbafcbe85 --- /dev/null +++ b/src/content-tags-drawer/index.scss @@ -0,0 +1,2 @@ +@import "content-tags-drawer/TagBubble"; +@import "content-tags-drawer/tags-sidebar/TagsTree"; diff --git a/src/content-tags-drawer/messages.js b/src/content-tags-drawer/messages.js index c54e6b7bcc..90f4db9e0e 100644 --- a/src/content-tags-drawer/messages.js +++ b/src/content-tags-drawer/messages.js @@ -33,6 +33,10 @@ const messages = defineMessages({ id: 'course-authoring.content-tags-drawer.content-tags-collapsible.selectable-box.selection.aria.label', defaultMessage: 'taxonomy tags selection', }, + manageTagsButton: { + id: 'course-authoring.content-tags-drawer.button.manage', + defaultMessage: 'Manage tags', + }, }); export default messages; diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx new file mode 100644 index 0000000000..4895ac0526 --- /dev/null +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx @@ -0,0 +1,102 @@ +import { useState, useMemo } from 'react'; +import { + Card, Stack, Button, Sheet, Collapsible +} from '@openedx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { useParams } from 'react-router-dom'; +import { ContentTagsDrawer } from '..'; + +import messages from '../messages'; +import { useContentTaxonomyTagsData } from '../data/apiHooks'; +import Loading from '../../generic/Loading'; +import TagsTree from './TagsTree'; + +const TagsSidebarBody = () => { + const intl = useIntl(); + const [showManageTags, setShowManageTags] = useState(false); + const contentId = useParams().blockId; + const onClose = () => setShowManageTags(false); + + const { + data: contentTaxonomyTagsData, + isSuccess: isContentTaxonomyTagsLoaded, + } = useContentTaxonomyTagsData(contentId); + + const buildTagsTree = (contentTags) => { + const resultTree = {}; + contentTags.forEach(item => { + let currentLevel = resultTree; + + item.lineage.forEach((key) => { + if (!currentLevel[key]) { + currentLevel[key] = { + children: {}, + canChangeObjecttag: item.canChangeObjecttag, + canDeleteObjecttag: item.canDeleteObjecttag, + }; + } + + currentLevel = currentLevel[key].children; + }); + }); + + return resultTree; + }; + + const tree = useMemo(() => { + const result = []; + if (contentTaxonomyTagsData) { + contentTaxonomyTagsData.taxonomies.forEach((taxonomy) => { + result.push({ + ...taxonomy, + tags: buildTagsTree(taxonomy.tags), + }); + }); + } + return result; + }, [contentTaxonomyTagsData]); + + return ( + <> + + + { isContentTaxonomyTagsLoaded + ? ( + + {tree.map((taxonomy) => ( +
+ + + +
+ ))} +
+ ) + : } + + +
+
+ + + + + ); +}; + +TagsSidebarBody.propTypes = {}; + +export default TagsSidebarBody; diff --git a/src/content-tags-drawer/tags-sidebar/TagsTree.jsx b/src/content-tags-drawer/tags-sidebar/TagsTree.jsx new file mode 100644 index 0000000000..a71217a627 --- /dev/null +++ b/src/content-tags-drawer/tags-sidebar/TagsTree.jsx @@ -0,0 +1,34 @@ +import PropTypes from 'prop-types'; + +const TagsTree = ({ tags, tabNumber }) => { + if (tags === undefined) { + return null; + } + + // Generate tabs for the parents of this tree + const tabs = Array.from({ + length: tabNumber, + }).map(() => ); + + return ( +
+ {Object.keys(tags).map((key) => ( +
+ {tabs}{key} + { tags[key].children && } +
+ ))} +
+ ); +}; + +TagsTree.propTypes = { + tags: PropTypes.shape({}).isRequired, + tabNumber: PropTypes.number, +}; + +TagsTree.defaultProps = { + tabNumber: 0, +}; + +export default TagsTree; diff --git a/src/content-tags-drawer/tags-sidebar/TagsTree.scss b/src/content-tags-drawer/tags-sidebar/TagsTree.scss new file mode 100644 index 0000000000..bae4290bab --- /dev/null +++ b/src/content-tags-drawer/tags-sidebar/TagsTree.scss @@ -0,0 +1,6 @@ +.tags-tree { + .tab { + display: inline-block; + margin-left: 2em; + } +} diff --git a/src/content-tags-drawer/tags-sidebar/index.js b/src/content-tags-drawer/tags-sidebar/index.js new file mode 100644 index 0000000000..a9f6c1af2c --- /dev/null +++ b/src/content-tags-drawer/tags-sidebar/index.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as TagsSidebarBody } from './TagsSidebarBody'; diff --git a/src/course-unit/sidebar/components/TagsSidebarBody.jsx b/src/course-unit/sidebar/components/TagsSidebarBody.jsx deleted file mode 100644 index a28ff53427..0000000000 --- a/src/course-unit/sidebar/components/TagsSidebarBody.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useState } from 'react'; -import { - Card, Stack, Button, Sheet, -} from '@openedx/paragon'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { useParams } from 'react-router-dom'; -import { ContentTagsDrawer } from '../../../content-tags-drawer'; - -import messages from '../messages'; - -const TagsSidebarBody = () => { - const intl = useIntl(); - const [showManageTags, setShowManageTags] = useState(false); - const contentId = useParams().blockId; - const onClose = () => setShowManageTags(false); - - return ( - <> - - - Test - - - - - - - - ); -}; - -TagsSidebarBody.propTypes = {}; - -export default TagsSidebarBody; diff --git a/src/course-unit/sidebar/index.jsx b/src/course-unit/sidebar/index.jsx index 65b0f5b744..f85f59cdac 100644 --- a/src/course-unit/sidebar/index.jsx +++ b/src/course-unit/sidebar/index.jsx @@ -12,7 +12,7 @@ import { PUBLISH_TYPES } from '../constants'; import { SidebarBody, SidebarFooter, SidebarHeader } from './components'; import useCourseUnitData from './hooks'; import messages from './messages'; -import TagsSidebarBody from './components/TagsSidebarBody'; +import { TagsSidebarBody } from '../../content-tags-drawer/tags-sidebar'; const Sidebar = ({ variant, blockId, ...props }) => { const { diff --git a/src/index.scss b/src/index.scss index feedc94249..2693a16293 100755 --- a/src/index.scss +++ b/src/index.scss @@ -19,7 +19,7 @@ @import "import-page/CourseImportPage"; @import "taxonomy"; @import "files-and-videos"; -@import "content-tags-drawer/TagBubble"; +@import "content-tags-drawer"; @import "course-outline/CourseOutline"; @import "course-unit/CourseUnit"; @import "course-checklist/CourseChecklist"; From 4bed61a43a2555c8cb3b605d7834f6896b93e2fa Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 22 Feb 2024 14:44:30 -0500 Subject: [PATCH 03/17] feat: Adding styles to the TagsTree --- src/content-tags-drawer/index.scss | 2 +- .../tags-sidebar/TagsSidebarBody.jsx | 19 ++++++++---- .../tags-sidebar/TagsSidebarBody.scss | 31 +++++++++++++++++++ .../tags-sidebar/TagsTree.jsx | 20 +++++++----- .../tags-sidebar/TagsTree.scss | 6 ---- src/course-unit/sidebar/index.jsx | 4 ++- 6 files changed, 60 insertions(+), 22 deletions(-) create mode 100644 src/content-tags-drawer/tags-sidebar/TagsSidebarBody.scss delete mode 100644 src/content-tags-drawer/tags-sidebar/TagsTree.scss diff --git a/src/content-tags-drawer/index.scss b/src/content-tags-drawer/index.scss index 9cbafcbe85..e0cb6914b7 100644 --- a/src/content-tags-drawer/index.scss +++ b/src/content-tags-drawer/index.scss @@ -1,2 +1,2 @@ @import "content-tags-drawer/TagBubble"; -@import "content-tags-drawer/tags-sidebar/TagsTree"; +@import "content-tags-drawer/tags-sidebar/TagsSidebarBody"; diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx index 4895ac0526..b7103752d9 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx @@ -1,14 +1,15 @@ import { useState, useMemo } from 'react'; import { - Card, Stack, Button, Sheet, Collapsible + Card, Stack, Button, Sheet, Collapsible, Icon, } from '@openedx/paragon'; +import { ArrowDropDown, ArrowDropUp } from '@openedx/paragon/icons'; import { useIntl } from '@edx/frontend-platform/i18n'; import { useParams } from 'react-router-dom'; import { ContentTagsDrawer } from '..'; import messages from '../messages'; import { useContentTaxonomyTagsData } from '../data/apiHooks'; -import Loading from '../../generic/Loading'; +import { LoadingSpinner } from '../../generic/Loading'; import TagsTree from './TagsTree'; const TagsSidebarBody = () => { @@ -66,9 +67,11 @@ const TagsSidebarBody = () => { {tree.map((taxonomy) => (
} + iconWhenOpen={} > @@ -76,9 +79,13 @@ const TagsSidebarBody = () => { ))} ) - : } + : ( +
+ +
+ )} - diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.scss b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.scss new file mode 100644 index 0000000000..a3ab86fd84 --- /dev/null +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.scss @@ -0,0 +1,31 @@ +.tags-sidebar { + .course-unit-sidebar-header { + padding-bottom: .25rem !important; + } + + .tags-sidebar-body { + padding-left: .7rem !important; + + .tags-sidebar-taxonomy { + border: none; + + .collapsible-trigger { + font-weight: bold; + border: none; + justify-content: start; + padding-left: 0; + padding-bottom: 0; + + .collapsible-icon { + order: -1; + margin-left: 0; + } + } + + .collapsible-body { + padding-top: 0; + padding-bottom: 0; + } + } + } +} diff --git a/src/content-tags-drawer/tags-sidebar/TagsTree.jsx b/src/content-tags-drawer/tags-sidebar/TagsTree.jsx index a71217a627..d503f00827 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsTree.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsTree.jsx @@ -1,21 +1,25 @@ import PropTypes from 'prop-types'; +import { Icon } from '@openedx/paragon'; +import { Tag } from '@openedx/paragon/icons'; -const TagsTree = ({ tags, tabNumber }) => { +const TagsTree = ({ tags, rootDepth }) => { if (tags === undefined) { return null; } // Generate tabs for the parents of this tree const tabs = Array.from({ - length: tabNumber, - }).map(() => ); + length: rootDepth, + }).map(() => ); return (
{Object.keys(tags).map((key) => ( -
- {tabs}{key} - { tags[key].children && } +
+
+ {tabs}{key} +
+ { tags[key].children && }
))}
@@ -24,11 +28,11 @@ const TagsTree = ({ tags, tabNumber }) => { TagsTree.propTypes = { tags: PropTypes.shape({}).isRequired, - tabNumber: PropTypes.number, + rootDepth: PropTypes.number, }; TagsTree.defaultProps = { - tabNumber: 0, + rootDepth: 0, }; export default TagsTree; diff --git a/src/content-tags-drawer/tags-sidebar/TagsTree.scss b/src/content-tags-drawer/tags-sidebar/TagsTree.scss deleted file mode 100644 index bae4290bab..0000000000 --- a/src/content-tags-drawer/tags-sidebar/TagsTree.scss +++ /dev/null @@ -1,6 +0,0 @@ -.tags-tree { - .tab { - display: inline-block; - margin-left: 2em; - } -} diff --git a/src/course-unit/sidebar/index.jsx b/src/course-unit/sidebar/index.jsx index f85f59cdac..de14f2aa78 100644 --- a/src/course-unit/sidebar/index.jsx +++ b/src/course-unit/sidebar/index.jsx @@ -45,6 +45,7 @@ const Sidebar = ({ variant, blockId, ...props }) => { let sidebarBody; let hideFooter = false; let hideIcon = false; + let className = ''; switch (variant) { case 'publish': sidebarTitle = title; @@ -71,6 +72,7 @@ const Sidebar = ({ variant, blockId, ...props }) => { ); hideFooter = true; hideIcon = true; + className = 'tags-sidebar'; break; default: break; @@ -78,7 +80,7 @@ const Sidebar = ({ variant, blockId, ...props }) => { return ( Date: Thu, 22 Feb 2024 15:38:14 -0500 Subject: [PATCH 04/17] feat: TagsSidebarHeader created --- src/content-tags-drawer/messages.js | 4 +++ .../tags-sidebar/TagsSidebarHeader.jsx | 29 +++++++++++++++ .../sidebar/components/SidebarHeader.jsx | 2 ++ src/course-unit/sidebar/index.jsx | 36 +++++++++++-------- src/course-unit/sidebar/messages.js | 8 ----- 5 files changed, 57 insertions(+), 22 deletions(-) create mode 100644 src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx diff --git a/src/content-tags-drawer/messages.js b/src/content-tags-drawer/messages.js index 90f4db9e0e..26df2c6d77 100644 --- a/src/content-tags-drawer/messages.js +++ b/src/content-tags-drawer/messages.js @@ -37,6 +37,10 @@ const messages = defineMessages({ id: 'course-authoring.content-tags-drawer.button.manage', defaultMessage: 'Manage tags', }, + tagsSidebarTitle: { + id: 'course-authoring.course-unit.sidebar.tags.title', + defaultMessage: 'Unit Tags', + }, }); export default messages; diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx b/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx new file mode 100644 index 0000000000..1d7dbe4f4e --- /dev/null +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx @@ -0,0 +1,29 @@ +import { Icon, Stack } from '@openedx/paragon'; +import { Tag } from '@openedx/paragon/icons'; +import { useParams } from 'react-router-dom'; +import { useIntl } from '@edx/frontend-platform/i18n'; + +import messages from '../messages'; + +const TagsSidebarHeader = () => { + const intl = useIntl(); + const contentId = useParams().blockId; + const tagCount = 0; + return ( + +

+ {intl.formatMessage(messages.tagsSidebarTitle)} +

+
+ + {tagCount} +
+
+ ); +}; + +TagsSidebarHeader.propTypes = {}; + +export default TagsSidebarHeader; diff --git a/src/course-unit/sidebar/components/SidebarHeader.jsx b/src/course-unit/sidebar/components/SidebarHeader.jsx index d94af33a46..b6b6feda03 100644 --- a/src/course-unit/sidebar/components/SidebarHeader.jsx +++ b/src/course-unit/sidebar/components/SidebarHeader.jsx @@ -1,9 +1,11 @@ import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import { Icon, Stack } from '@openedx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { getCourseUnitData } from '../../data/selectors'; import { getIconVariant } from '../utils'; +import messages from '../messages'; const SidebarHeader = ({ title, visibilityState, displayUnitLocation }) => { const intl = useIntl(); diff --git a/src/course-unit/sidebar/index.jsx b/src/course-unit/sidebar/index.jsx index de14f2aa78..d22adf00c6 100644 --- a/src/course-unit/sidebar/index.jsx +++ b/src/course-unit/sidebar/index.jsx @@ -11,8 +11,9 @@ import { getCourseUnitData } from '../data/selectors'; import { PUBLISH_TYPES } from '../constants'; import { SidebarBody, SidebarFooter, SidebarHeader } from './components'; import useCourseUnitData from './hooks'; -import messages from './messages'; import { TagsSidebarBody } from '../../content-tags-drawer/tags-sidebar'; +import TagsSidebarHeader from '../../content-tags-drawer/tags-sidebar/TagsSidebarHeader'; +import messages from './messages'; const Sidebar = ({ variant, blockId, ...props }) => { const { @@ -41,14 +42,18 @@ const Sidebar = ({ variant, blockId, ...props }) => { dispatch(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic)); }; - let sidebarTitle; + let sidebarHeader; let sidebarBody; let hideFooter = false; - let hideIcon = false; let className = ''; switch (variant) { case 'publish': - sidebarTitle = title; + sidebarHeader = ( + + ); sidebarBody = ( { ); break; case 'location': - sidebarTitle = intl.formatMessage(messages.sidebarHeaderUnitLocationTitle); + sidebarHeader = ( + + ); sidebarBody = ( ); break; case 'tags': - sidebarTitle = intl.formatMessage(messages.tagsSidebarTitle); + sidebarHeader = ( + + ); sidebarBody = ( ); hideFooter = true; - hideIcon = true; className = 'tags-sidebar'; break; default: @@ -85,11 +97,7 @@ const Sidebar = ({ variant, blockId, ...props }) => { })} {...props} > - + { sidebarHeader } { sidebarBody } { !hideFooter && ( @@ -97,7 +105,7 @@ const Sidebar = ({ variant, blockId, ...props }) => { locationId={locationId} openDiscardModal={openDiscardModal} openVisibleModal={openVisibleModal} - isDisplayUnitLocation={variant === 'location'} + displayUnitLocation={variant === 'location'} handlePublishing={handleCourseUnitPublish} visibleToStaffOnly={visibleToStaffOnly} /> diff --git a/src/course-unit/sidebar/messages.js b/src/course-unit/sidebar/messages.js index 872c506d53..7d9d161d5c 100644 --- a/src/course-unit/sidebar/messages.js +++ b/src/course-unit/sidebar/messages.js @@ -137,14 +137,6 @@ const messages = defineMessages({ id: 'course-authoring.course-unit.modal.make-visibility.description', defaultMessage: 'If the unit was previously published and released to students, any changes you made to the unit when it was hidden will now be visible to students. Do you want to proceed?', }, - tagsSidebarTitle: { - id: 'course-authoring.course-unit.sidebar.tags.title', - defaultMessage: 'Unit Tags', - }, - tagsSidebarManageButtonLabel: { - id: 'course-authoring.course-unit.sidebar.tags.button.manage', - defaultMessage: 'Manage tags', - }, }); export default messages; From 29ad22a72368e6cdaad8b953ad87e57b67322464 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Fri, 23 Feb 2024 11:52:49 -0500 Subject: [PATCH 05/17] feat: Add count on TagsSidebarHeader --- .../__mocks__/contentTaxonomyTagsCountMock.js | 3 +++ src/content-tags-drawer/__mocks__/index.js | 1 + src/content-tags-drawer/data/api.js | 11 +++++++++ src/content-tags-drawer/data/api.test.js | 12 ++++++++++ src/content-tags-drawer/data/apiHooks.jsx | 16 +++++++++++++ .../data/apiHooks.test.jsx | 19 +++++++++++++++ .../tags-sidebar/TagsSidebarHeader.jsx | 24 +++++++++++++------ 7 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 src/content-tags-drawer/__mocks__/contentTaxonomyTagsCountMock.js diff --git a/src/content-tags-drawer/__mocks__/contentTaxonomyTagsCountMock.js b/src/content-tags-drawer/__mocks__/contentTaxonomyTagsCountMock.js new file mode 100644 index 0000000000..3ce4d2050a --- /dev/null +++ b/src/content-tags-drawer/__mocks__/contentTaxonomyTagsCountMock.js @@ -0,0 +1,3 @@ +module.exports = { + 'block-v1:SampleTaxonomyOrg1+STC1+2023_1+type@vertical+block@aaf8b8eb86b54281aeeab12499d2cb0b': 20, +}; diff --git a/src/content-tags-drawer/__mocks__/index.js b/src/content-tags-drawer/__mocks__/index.js index 5ec3027386..0ef909f962 100644 --- a/src/content-tags-drawer/__mocks__/index.js +++ b/src/content-tags-drawer/__mocks__/index.js @@ -2,3 +2,4 @@ export { default as taxonomyTagsMock } from './taxonomyTagsMock'; export { default as contentTaxonomyTagsMock } from './contentTaxonomyTagsMock'; export { default as contentDataMock } from './contentDataMock'; export { default as updateContentTaxonomyTagsMock } from './updateContentTaxonomyTagsMock'; +export { default as contentTaxonomyTagsCountMock } from './contentTaxonomyTagsCountMock'; diff --git a/src/content-tags-drawer/data/api.js b/src/content-tags-drawer/data/api.js index 28bd7a36c8..3c3f09cc46 100644 --- a/src/content-tags-drawer/data/api.js +++ b/src/content-tags-drawer/data/api.js @@ -31,6 +31,7 @@ export const getTaxonomyTagsApiUrl = (taxonomyId, options = {}) => { export const getContentTaxonomyTagsApiUrl = (contentId) => new URL(`api/content_tagging/v1/object_tags/${contentId}/`, getApiBaseUrl()).href; export const getXBlockContentDataApiURL = (contentId) => new URL(`/xblock/outline/${contentId}`, getApiBaseUrl()).href; export const getLibraryContentDataApiUrl = (contentId) => new URL(`/api/libraries/v2/blocks/${contentId}/`, getApiBaseUrl()).href; +export const getContentTaxonomyTagsCountApiUrl = (contentId) => new URL(`api/content_tagging/v1/object_tag_counts/${contentId}/?count_implicit`, getApiBaseUrl()).href; /** * Get all tags that belong to taxonomy. @@ -54,6 +55,16 @@ export async function getContentTaxonomyTagsData(contentId) { return camelCaseObject(data[contentId]); } +/** + * Get the count of tags that are applied to the content object + * @param {string} contentId The id of the content object to fetch the count of the applied tags for + * @returns {Promise} + */ +export async function getContentTaxonomyTagsCount(contentId) { + const { data } = await getAuthenticatedHttpClient().get(getContentTaxonomyTagsCountApiUrl(contentId)); + return camelCaseObject(data[contentId]); +} + /** * Fetch meta data (eg: display_name) about the content object (unit/compoenent) * @param {string} contentId The id of the content object (unit/component) diff --git a/src/content-tags-drawer/data/api.test.js b/src/content-tags-drawer/data/api.test.js index 9fa88dcb79..2c0dd3849a 100644 --- a/src/content-tags-drawer/data/api.test.js +++ b/src/content-tags-drawer/data/api.test.js @@ -6,6 +6,7 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { taxonomyTagsMock, contentTaxonomyTagsMock, + contentTaxonomyTagsCountMock, contentDataMock, updateContentTaxonomyTagsMock, } from '../__mocks__'; @@ -19,6 +20,8 @@ import { getContentTaxonomyTagsData, getContentData, updateContentTaxonomyTags, + getContentTaxonomyTagsCountApiUrl, + getContentTaxonomyTagsCount, } from './api'; let axiosMock; @@ -88,6 +91,15 @@ describe('content tags drawer api calls', () => { expect(result).toEqual(contentTaxonomyTagsMock[contentId]); }); + it('should get content taxonomy tags count', async () => { + const contentId = 'block-v1:SampleTaxonomyOrg1+STC1+2023_1+type@vertical+block@aaf8b8eb86b54281aeeab12499d2cb0b'; + axiosMock.onGet(getContentTaxonomyTagsCountApiUrl(contentId)).reply(200, contentTaxonomyTagsCountMock); + const result = await getContentTaxonomyTagsCount(contentId); + + expect(axiosMock.history.get[0].url).toEqual(getContentTaxonomyTagsCountApiUrl(contentId)); + expect(result).toEqual(contentTaxonomyTagsCountMock[contentId]); + }); + it('should get content data for course component', async () => { const contentId = 'block-v1:SampleTaxonomyOrg1+STC1+2023_1+type@vertical+block@aaf8b8eb86b54281aeeab12499d2cb0b'; axiosMock.onGet(getXBlockContentDataApiURL(contentId)).reply(200, contentDataMock); diff --git a/src/content-tags-drawer/data/apiHooks.jsx b/src/content-tags-drawer/data/apiHooks.jsx index 82e9a700ca..e5dab13fa8 100644 --- a/src/content-tags-drawer/data/apiHooks.jsx +++ b/src/content-tags-drawer/data/apiHooks.jsx @@ -11,6 +11,7 @@ import { getContentTaxonomyTagsData, getContentData, updateContentTaxonomyTags, + getContentTaxonomyTagsCount, } from './api'; /** @typedef {import("../../taxonomy/tag-list/data/types.mjs").TagListData} TagListData */ @@ -105,6 +106,17 @@ export const useContentTaxonomyTagsData = (contentId) => ( }) ); +/** + * Build the query to get the count og taxonomy tags applied to the content object + * @param {string} contentId The ID of the content object to fetch the count of the applied tags for + */ +export const useContentTaxonomyTagsCount = (contentId) => ( + useQuery({ + queryKey: ['contentTaxonomyTagsCount', contentId], + queryFn: () => getContentTaxonomyTagsCount(contentId), + }) +); + /** * Builds the query to get meta data about the content object * @param {string} contentId The id of the content object (unit/component) @@ -137,8 +149,12 @@ export const useContentTaxonomyTagsUpdater = (contentId, taxonomyId) => { mutationFn: ({ tags }) => updateContentTaxonomyTags(contentId, taxonomyId, tags), onSettled: /* istanbul ignore next */ () => { queryClient.invalidateQueries({ queryKey: ['contentTaxonomyTags', contentId] }); +<<<<<<< HEAD /// Invalidate query with pattern on course outline queryClient.invalidateQueries({ queryKey: ['unitTagsCount'] }); +======= + queryClient.invalidateQueries({ queryKey: ['contentTaxonomyTagsCount', contentId] }); +>>>>>>> 92a9f4d9 (feat: Add count on TagsSidebarHeader) }, }); }; diff --git a/src/content-tags-drawer/data/apiHooks.test.jsx b/src/content-tags-drawer/data/apiHooks.test.jsx index 4e12ef5ea5..127d71cc5b 100644 --- a/src/content-tags-drawer/data/apiHooks.test.jsx +++ b/src/content-tags-drawer/data/apiHooks.test.jsx @@ -6,6 +6,7 @@ import { useContentTaxonomyTagsData, useContentData, useContentTaxonomyTagsUpdater, + useContentTaxonomyTagsCount, } from './apiHooks'; import { updateContentTaxonomyTags } from './api'; @@ -134,6 +135,24 @@ describe('useContentTaxonomyTagsData', () => { }); }); +describe('useContentTaxonomyTagsCount', () => { + it('should return success response', () => { + useQuery.mockReturnValueOnce({ isSuccess: true, data: 'data' }); + const contentId = '123'; + const result = useContentTaxonomyTagsCount(contentId); + + expect(result).toEqual({ isSuccess: true, data: 'data' }); + }); + + it('should return failure response', () => { + useQuery.mockReturnValueOnce({ isSuccess: false }); + const contentId = '123'; + const result = useContentTaxonomyTagsCount(contentId); + + expect(result).toEqual({ isSuccess: false }); + }); +}); + describe('useContentData', () => { it('should return success response', () => { useQuery.mockReturnValueOnce({ isSuccess: true, data: 'data' }); diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx b/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx index 1d7dbe4f4e..7c589f8ef9 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx @@ -4,22 +4,32 @@ import { useParams } from 'react-router-dom'; import { useIntl } from '@edx/frontend-platform/i18n'; import messages from '../messages'; +import { useContentTaxonomyTagsCount } from '../data/apiHooks'; const TagsSidebarHeader = () => { const intl = useIntl(); const contentId = useParams().blockId; - const tagCount = 0; + + const { + data: contentTaxonomyTagsCount, + isSuccess: isContentTaxonomyTagsCountLoaded, + } = useContentTaxonomyTagsCount(contentId); + return (

{intl.formatMessage(messages.tagsSidebarTitle)}

-
- - {tagCount} -
+ { isContentTaxonomyTagsCountLoaded + && ( +
+ + {contentTaxonomyTagsCount} +
+ )}
); }; From 56af8924e3eb3fc79bf4bde686932c4eee63ff73 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Mon, 26 Feb 2024 16:24:23 -0500 Subject: [PATCH 06/17] test: Tests for new components added --- .../__mocks__/contentTaxonomyTagsTreeMock.js | 35 ++++++++++++ src/content-tags-drawer/__mocks__/index.js | 1 + src/content-tags-drawer/data/api.js | 5 +- src/content-tags-drawer/data/api.test.js | 9 +++ .../tags-sidebar/TagsSidebarBody.jsx | 4 +- .../tags-sidebar/TagsSidebarBody.test.jsx | 55 +++++++++++++++++++ .../tags-sidebar/TagsSidebarHeader.jsx | 2 +- .../tags-sidebar/TagsSidebarHeader.test.jsx | 46 ++++++++++++++++ .../tags-sidebar/TagsTree.jsx | 2 +- .../tags-sidebar/TagsTree.test.jsx | 13 +++++ 10 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 src/content-tags-drawer/__mocks__/contentTaxonomyTagsTreeMock.js create mode 100644 src/content-tags-drawer/tags-sidebar/TagsSidebarBody.test.jsx create mode 100644 src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.test.jsx create mode 100644 src/content-tags-drawer/tags-sidebar/TagsTree.test.jsx diff --git a/src/content-tags-drawer/__mocks__/contentTaxonomyTagsTreeMock.js b/src/content-tags-drawer/__mocks__/contentTaxonomyTagsTreeMock.js new file mode 100644 index 0000000000..687e3d357b --- /dev/null +++ b/src/content-tags-drawer/__mocks__/contentTaxonomyTagsTreeMock.js @@ -0,0 +1,35 @@ +module.exports = { + 'hierarchical taxonomy tag 1': { + children: { + 'hierarchical taxonomy tag 1.7': { + children: { + 'hierarchical taxonomy tag 1.7.59': { + children: {}, + }, + }, + }, + }, + }, + 'hierarchical taxonomy tag 2': { + children: { + 'hierarchical taxonomy tag 2.13': { + children: { + 'hierarchical taxonomy tag 2.13.46': { + children: {}, + }, + }, + }, + }, + }, + 'hierarchical taxonomy tag 3': { + children: { + 'hierarchical taxonomy tag 3.4': { + children: { + 'hierarchical taxonomy tag 3.4.50': { + children: {}, + }, + }, + }, + }, + }, +}; diff --git a/src/content-tags-drawer/__mocks__/index.js b/src/content-tags-drawer/__mocks__/index.js index 0ef909f962..8c4274d643 100644 --- a/src/content-tags-drawer/__mocks__/index.js +++ b/src/content-tags-drawer/__mocks__/index.js @@ -3,3 +3,4 @@ export { default as contentTaxonomyTagsMock } from './contentTaxonomyTagsMock'; export { default as contentDataMock } from './contentDataMock'; export { default as updateContentTaxonomyTagsMock } from './updateContentTaxonomyTagsMock'; export { default as contentTaxonomyTagsCountMock } from './contentTaxonomyTagsCountMock'; +export { default as contentTaxonomyTagsTreeMock } from './contentTaxonomyTagsTreeMock'; diff --git a/src/content-tags-drawer/data/api.js b/src/content-tags-drawer/data/api.js index 3c3f09cc46..86ee7746d9 100644 --- a/src/content-tags-drawer/data/api.js +++ b/src/content-tags-drawer/data/api.js @@ -62,7 +62,10 @@ export async function getContentTaxonomyTagsData(contentId) { */ export async function getContentTaxonomyTagsCount(contentId) { const { data } = await getAuthenticatedHttpClient().get(getContentTaxonomyTagsCountApiUrl(contentId)); - return camelCaseObject(data[contentId]); + if (contentId in data) { + return camelCaseObject(data[contentId]); + } + return 0; } /** diff --git a/src/content-tags-drawer/data/api.test.js b/src/content-tags-drawer/data/api.test.js index 2c0dd3849a..c092fb0d23 100644 --- a/src/content-tags-drawer/data/api.test.js +++ b/src/content-tags-drawer/data/api.test.js @@ -100,6 +100,15 @@ describe('content tags drawer api calls', () => { expect(result).toEqual(contentTaxonomyTagsCountMock[contentId]); }); + it('should get content taxonomy tags count as cero', async () => { + const contentId = 'block-v1:SampleTaxonomyOrg1+STC1+2023_1+type@vertical+block@aaf8b8eb86b54281aeeab12499d2cb0b'; + axiosMock.onGet(getContentTaxonomyTagsCountApiUrl(contentId)).reply(200, {}); + const result = await getContentTaxonomyTagsCount(contentId); + + expect(axiosMock.history.get[0].url).toEqual(getContentTaxonomyTagsCountApiUrl(contentId)); + expect(result).toEqual(0); + }); + it('should get content data for course component', async () => { const contentId = 'block-v1:SampleTaxonomyOrg1+STC1+2023_1+type@vertical+block@aaf8b8eb86b54281aeeab12499d2cb0b'; axiosMock.onGet(getXBlockContentDataApiURL(contentId)).reply(200, contentDataMock); diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx index b7103752d9..50ff0959a2 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx @@ -46,7 +46,7 @@ const TagsSidebarBody = () => { const tree = useMemo(() => { const result = []; - if (contentTaxonomyTagsData) { + if (isContentTaxonomyTagsLoaded && contentTaxonomyTagsData) { contentTaxonomyTagsData.taxonomies.forEach((taxonomy) => { result.push({ ...taxonomy, @@ -55,7 +55,7 @@ const TagsSidebarBody = () => { }); } return result; - }, [contentTaxonomyTagsData]); + }, [isContentTaxonomyTagsLoaded, contentTaxonomyTagsData]); return ( <> diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.test.jsx b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.test.jsx new file mode 100644 index 0000000000..32be90bf44 --- /dev/null +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.test.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import TagsSidebarBody from './TagsSidebarBody'; +import { useContentTaxonomyTagsData } from '../data/apiHooks'; +import { contentTaxonomyTagsMock } from '../__mocks__'; + +const contentId = 'block-v1:SampleTaxonomyOrg1+STC1+2023_1+type@vertical+block@aaf8b8eb86b54281aeeab12499d2cb0b'; + +jest.mock('../data/apiHooks', () => ({ + useContentTaxonomyTagsData: jest.fn(() => ({ + isSuccess: false, + data: {}, + })), +})); +jest.mock('../ContentTagsDrawer', () => jest.fn(() =>
Mocked ContentTagsDrawer
)); + +const RootWrapper = () => ( + + + +); + +describe('', () => { + it('shows spinner before the content data query is complete', () => { + render(); + expect(screen.getByRole('status')).toBeInTheDocument(); + }); + + it('should render data after wuery is complete', () => { + useContentTaxonomyTagsData.mockReturnValue({ + isSuccess: true, + data: contentTaxonomyTagsMock[contentId], + }); + render(); + const taxonomyButton = screen.getByRole('button', { name: /hierarchicaltaxonomy/i }); + expect(taxonomyButton).toBeInTheDocument(); + + /// ContentTagsDrawer must be closed + expect(screen.queryByText('Mocked ContentTagsDrawer')).not.toBeInTheDocument(); + }); + + it('should open ContentTagsDrawer', () => { + useContentTaxonomyTagsData.mockReturnValue({ + isSuccess: true, + data: contentTaxonomyTagsMock[contentId], + }); + render(); + + const manageButton = screen.getByRole('button', { name: /manage tags/i }); + fireEvent.click(manageButton); + + expect(screen.getByText('Mocked ContentTagsDrawer')).toBeInTheDocument(); + }); +}); diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx b/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx index 7c589f8ef9..ff318fc062 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx @@ -20,7 +20,7 @@ const TagsSidebarHeader = () => {

{intl.formatMessage(messages.tagsSidebarTitle)}

- { isContentTaxonomyTagsCountLoaded + { isContentTaxonomyTagsCountLoaded && contentTaxonomyTagsCount !== 0 && (
({ + useContentTaxonomyTagsCount: jest.fn(() => ({ + isSuccess: false, + data: 17, + })), +})); + +const RootWrapper = () => ( + + + +); + +describe('', () => { + it('should not render count on loading', () => { + render(); + expect(screen.getByRole('heading', { name: /unit tags/i })).toBeInTheDocument(); + expect(screen.queryByText('17')).not.toBeInTheDocument(); + }); + + it('should render count after query is complete', () => { + useContentTaxonomyTagsCount.mockReturnValue({ + isSuccess: true, + data: 17, + }); + render(); + expect(screen.getByRole('heading', { name: /unit tags/i })).toBeInTheDocument(); + expect(screen.getByText('17')).toBeInTheDocument(); + }); + + it('should not render count if is cero', () => { + useContentTaxonomyTagsCount.mockReturnValue({ + isSuccess: true, + data: 0, + }); + render(); + expect(screen.getByRole('heading', { name: /unit tags/i })).toBeInTheDocument(); + expect(screen.queryByText('0')).not.toBeInTheDocument(); + }); +}); diff --git a/src/content-tags-drawer/tags-sidebar/TagsTree.jsx b/src/content-tags-drawer/tags-sidebar/TagsTree.jsx index d503f00827..798ae8cf60 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsTree.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsTree.jsx @@ -3,7 +3,7 @@ import { Icon } from '@openedx/paragon'; import { Tag } from '@openedx/paragon/icons'; const TagsTree = ({ tags, rootDepth }) => { - if (tags === undefined) { + if (Object.keys(tags).length === 0) { return null; } diff --git a/src/content-tags-drawer/tags-sidebar/TagsTree.test.jsx b/src/content-tags-drawer/tags-sidebar/TagsTree.test.jsx new file mode 100644 index 0000000000..0ca8c5333a --- /dev/null +++ b/src/content-tags-drawer/tags-sidebar/TagsTree.test.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import TagsTree from './TagsTree'; +import { contentTaxonomyTagsTreeMock } from '../__mocks__'; + +describe('', () => { + it('should render component and tags correctly', () => { + render(); + expect(screen.getByText('hierarchical taxonomy tag 1')).toBeInTheDocument(); + expect(screen.getByText('hierarchical taxonomy tag 2.13')).toBeInTheDocument(); + expect(screen.getByText('hierarchical taxonomy tag 3.4.50')).toBeInTheDocument(); + }); +}); From f808a843da0fea40b7e0e25aad50eda4c93a8b69 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Tue, 27 Feb 2024 09:37:24 -0500 Subject: [PATCH 07/17] style: Update tags count with opacity when the count is zero --- src/content-tags-drawer/index.scss | 2 +- .../{TagsSidebarBody.scss => TagsSidebar.scss} | 4 ++++ .../tags-sidebar/TagsSidebarHeader.jsx | 8 ++++++-- .../tags-sidebar/TagsSidebarHeader.test.jsx | 10 ---------- 4 files changed, 11 insertions(+), 13 deletions(-) rename src/content-tags-drawer/tags-sidebar/{TagsSidebarBody.scss => TagsSidebar.scss} (87%) diff --git a/src/content-tags-drawer/index.scss b/src/content-tags-drawer/index.scss index e0cb6914b7..829a0f18ae 100644 --- a/src/content-tags-drawer/index.scss +++ b/src/content-tags-drawer/index.scss @@ -1,2 +1,2 @@ @import "content-tags-drawer/TagBubble"; -@import "content-tags-drawer/tags-sidebar/TagsSidebarBody"; +@import "content-tags-drawer/tags-sidebar/TagsSidebar"; diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.scss b/src/content-tags-drawer/tags-sidebar/TagsSidebar.scss similarity index 87% rename from src/content-tags-drawer/tags-sidebar/TagsSidebarBody.scss rename to src/content-tags-drawer/tags-sidebar/TagsSidebar.scss index a3ab86fd84..c433bc43c7 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.scss +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebar.scss @@ -1,6 +1,10 @@ .tags-sidebar { .course-unit-sidebar-header { padding-bottom: .25rem !important; + + .course-unit-sidebar-header-count.zero-count { + opacity: .4; + } } .tags-sidebar-body { diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx b/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx index ff318fc062..0dabd64b64 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx @@ -2,6 +2,7 @@ import { Icon, Stack } from '@openedx/paragon'; import { Tag } from '@openedx/paragon/icons'; import { useParams } from 'react-router-dom'; import { useIntl } from '@edx/frontend-platform/i18n'; +import classNames from 'classnames'; import messages from '../messages'; import { useContentTaxonomyTagsCount } from '../data/apiHooks'; @@ -20,9 +21,12 @@ const TagsSidebarHeader = () => {

{intl.formatMessage(messages.tagsSidebarTitle)}

- { isContentTaxonomyTagsCountLoaded && contentTaxonomyTagsCount !== 0 + { isContentTaxonomyTagsCountLoaded && ( -
+
', () => { expect(screen.getByRole('heading', { name: /unit tags/i })).toBeInTheDocument(); expect(screen.getByText('17')).toBeInTheDocument(); }); - - it('should not render count if is cero', () => { - useContentTaxonomyTagsCount.mockReturnValue({ - isSuccess: true, - data: 0, - }); - render(); - expect(screen.getByRole('heading', { name: /unit tags/i })).toBeInTheDocument(); - expect(screen.queryByText('0')).not.toBeInTheDocument(); - }); }); From 139e978eee01530735ddadcd11973b750d08f049 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Tue, 27 Feb 2024 10:11:35 -0500 Subject: [PATCH 08/17] fix: Warnings on console --- .../tags-sidebar/TagsSidebarBody.jsx | 4 +-- .../tags-sidebar/TagsTree.jsx | 30 ++++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx index 50ff0959a2..d981d0353c 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx @@ -65,7 +65,7 @@ const TagsSidebarBody = () => { ? ( {tree.map((taxonomy) => ( -
+
{ iconWhenClosed={} iconWhenOpen={} > - +
))} diff --git a/src/content-tags-drawer/tags-sidebar/TagsTree.jsx b/src/content-tags-drawer/tags-sidebar/TagsTree.jsx index 798ae8cf60..9647ff949a 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsTree.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsTree.jsx @@ -2,24 +2,32 @@ import PropTypes from 'prop-types'; import { Icon } from '@openedx/paragon'; import { Tag } from '@openedx/paragon/icons'; -const TagsTree = ({ tags, rootDepth }) => { +const TagsTree = ({ tags, rootDepth, parentKey }) => { if (Object.keys(tags).length === 0) { return null; } - // Generate tabs for the parents of this tree - const tabs = Array.from({ - length: rootDepth, - }).map(() => ); + // Used to Generate tabs for the parents of this tree + const tabsNumberArray = Array.from({ length: rootDepth }, (_, index) => index + 1); return ( -
+
{Object.keys(tags).map((key) => ( -
-
- {tabs}{key} +
+
+ { + tabsNumberArray.map((index) => ) + } + {key}
- { tags[key].children && } + { tags[key].children + && ( + + )}
))}
@@ -28,11 +36,13 @@ const TagsTree = ({ tags, rootDepth }) => { TagsTree.propTypes = { tags: PropTypes.shape({}).isRequired, + parentKey: PropTypes.string, rootDepth: PropTypes.number, }; TagsTree.defaultProps = { rootDepth: 0, + parentKey: undefined, }; export default TagsTree; From 14fd7cd3a755b8c6bd466305a4dc5a200afaa681 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Tue, 27 Feb 2024 11:27:52 -0500 Subject: [PATCH 09/17] refactor: Extract tag count component as generic --- src/content-tags-drawer/data/apiHooks.jsx | 3 --- .../tags-sidebar/TagsSidebar.scss | 4 ---- .../tags-sidebar/TagsSidebarHeader.jsx | 18 +++--------------- src/generic/styles.scss | 1 + 4 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/content-tags-drawer/data/apiHooks.jsx b/src/content-tags-drawer/data/apiHooks.jsx index e5dab13fa8..5c24c0aa6b 100644 --- a/src/content-tags-drawer/data/apiHooks.jsx +++ b/src/content-tags-drawer/data/apiHooks.jsx @@ -149,12 +149,9 @@ export const useContentTaxonomyTagsUpdater = (contentId, taxonomyId) => { mutationFn: ({ tags }) => updateContentTaxonomyTags(contentId, taxonomyId, tags), onSettled: /* istanbul ignore next */ () => { queryClient.invalidateQueries({ queryKey: ['contentTaxonomyTags', contentId] }); -<<<<<<< HEAD /// Invalidate query with pattern on course outline queryClient.invalidateQueries({ queryKey: ['unitTagsCount'] }); -======= queryClient.invalidateQueries({ queryKey: ['contentTaxonomyTagsCount', contentId] }); ->>>>>>> 92a9f4d9 (feat: Add count on TagsSidebarHeader) }, }); }; diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebar.scss b/src/content-tags-drawer/tags-sidebar/TagsSidebar.scss index c433bc43c7..a3ab86fd84 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsSidebar.scss +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebar.scss @@ -1,10 +1,6 @@ .tags-sidebar { .course-unit-sidebar-header { padding-bottom: .25rem !important; - - .course-unit-sidebar-header-count.zero-count { - opacity: .4; - } } .tags-sidebar-body { diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx b/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx index 0dabd64b64..83b5cd4457 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx @@ -1,11 +1,10 @@ -import { Icon, Stack } from '@openedx/paragon'; -import { Tag } from '@openedx/paragon/icons'; +import { Stack } from '@openedx/paragon'; import { useParams } from 'react-router-dom'; import { useIntl } from '@edx/frontend-platform/i18n'; -import classNames from 'classnames'; import messages from '../messages'; import { useContentTaxonomyTagsCount } from '../data/apiHooks'; +import TagCount from '../../generic/tag-count'; const TagsSidebarHeader = () => { const intl = useIntl(); @@ -22,18 +21,7 @@ const TagsSidebarHeader = () => { {intl.formatMessage(messages.tagsSidebarTitle)} { isContentTaxonomyTagsCountLoaded - && ( -
- - {contentTaxonomyTagsCount} -
- )} + && } ); }; diff --git a/src/generic/styles.scss b/src/generic/styles.scss index becfb9a77a..0a8dde0e9a 100644 --- a/src/generic/styles.scss +++ b/src/generic/styles.scss @@ -6,3 +6,4 @@ @import "./create-or-rerun-course/CreateOrRerunCourseForm"; @import "./WysiwygEditor"; @import "./course-stepper/CouseStepper"; +@import "./tag-count/TagCount"; From 67cfe582b4728c23393e523170b8106eb64cbd36 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 29 Feb 2024 12:28:37 -0500 Subject: [PATCH 10/17] chore: Fix rebase conflicts and issues --- src/course-unit/CourseUnit.jsx | 2 +- src/course-unit/CourseUnit.test.jsx | 32 +++++++++++++++++++ .../SequenceNavigationTabs.jsx | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/course-unit/CourseUnit.jsx b/src/course-unit/CourseUnit.jsx index 3a1b80ac25..431d729ab1 100644 --- a/src/course-unit/CourseUnit.jsx +++ b/src/course-unit/CourseUnit.jsx @@ -124,8 +124,8 @@ const CourseUnit = ({ courseId }) => { - + diff --git a/src/course-unit/CourseUnit.test.jsx b/src/course-unit/CourseUnit.test.jsx index 233bb63cc1..bf411de718 100644 --- a/src/course-unit/CourseUnit.test.jsx +++ b/src/course-unit/CourseUnit.test.jsx @@ -44,6 +44,7 @@ import deleteModalMessages from '../generic/delete-modal/messages'; import courseXBlockMessages from './course-xblock/messages'; import addComponentMessages from './add-component/messages'; import { PUBLISH_TYPES, UNIT_VISIBILITY_STATES } from './constants'; +import { getContentTaxonomyTagsApiUrl, getContentTaxonomyTagsCountApiUrl } from '../content-tags-drawer/data/api'; let axiosMock; let store; @@ -59,6 +60,31 @@ jest.mock('react-router-dom', () => ({ useNavigate: () => mockedUsedNavigate, })); +jest.mock('@tanstack/react-query', () => ({ + useQuery: jest.fn(({ queryKey }) => { + if (queryKey[0] === 'contentTaxonomyTags') { + return { + data: { + taxonomies: [], + }, + isSuccess: true, + }; + } if (queryKey[0] === 'contentTaxonomyTagsCount') { + return { + data: 17, + isSuccess: true, + }; + } + return { + data: {}, + isSuccess: true, + }; + }), + useQueryClient: jest.fn(() => ({ + setQueryData: jest.fn(), + })), +})); + const RootWrapper = () => ( @@ -92,6 +118,12 @@ describe('', () => { .onGet(getCourseVerticalChildrenApiUrl(blockId)) .reply(200, courseVerticalChildrenMock); await executeThunk(fetchCourseVerticalChildrenData(blockId), store.dispatch); + axiosMock + .onGet(getContentTaxonomyTagsApiUrl(blockId)) + .reply(200, {}); + axiosMock + .onGet(getContentTaxonomyTagsCountApiUrl(blockId)) + .reply(200, 17); }); it('render CourseUnit component correctly', async () => { diff --git a/src/course-unit/course-sequence/sequence-navigation/SequenceNavigationTabs.jsx b/src/course-unit/course-sequence/sequence-navigation/SequenceNavigationTabs.jsx index e0e79048dd..7565a8c0d1 100644 --- a/src/course-unit/course-sequence/sequence-navigation/SequenceNavigationTabs.jsx +++ b/src/course-unit/course-sequence/sequence-navigation/SequenceNavigationTabs.jsx @@ -1,6 +1,6 @@ import { useDispatch, useSelector } from 'react-redux'; import PropTypes from 'prop-types'; -import { Link, useNavigate } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Button } from '@openedx/paragon'; import { Plus as PlusIcon } from '@openedx/paragon/icons'; import { useIntl } from '@edx/frontend-platform/i18n'; From c83c2bca332538d8d8d0773a7cbf3d05a1aa6280 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Mon, 4 Mar 2024 12:33:01 -0500 Subject: [PATCH 11/17] style: Fix nits --- src/content-tags-drawer/messages.js | 2 +- src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx | 7 ++++--- src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx | 4 +++- src/content-tags-drawer/tags-sidebar/TagsTree.jsx | 2 ++ 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/content-tags-drawer/messages.js b/src/content-tags-drawer/messages.js index 26df2c6d77..bf55237f7f 100644 --- a/src/content-tags-drawer/messages.js +++ b/src/content-tags-drawer/messages.js @@ -35,7 +35,7 @@ const messages = defineMessages({ }, manageTagsButton: { id: 'course-authoring.content-tags-drawer.button.manage', - defaultMessage: 'Manage tags', + defaultMessage: 'Manage Tags', }, tagsSidebarTitle: { id: 'course-authoring.course-unit.sidebar.tags.title', diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx index d981d0353c..9304fbf503 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx @@ -1,4 +1,5 @@ -import { useState, useMemo } from 'react'; +// @ts-check +import React, { useState, useMemo } from 'react'; import { Card, Stack, Button, Sheet, Collapsible, Icon, } from '@openedx/paragon'; @@ -21,7 +22,7 @@ const TagsSidebarBody = () => { const { data: contentTaxonomyTagsData, isSuccess: isContentTaxonomyTagsLoaded, - } = useContentTaxonomyTagsData(contentId); + } = useContentTaxonomyTagsData(contentId || ''); const buildTagsTree = (contentTags) => { const resultTree = {}; @@ -85,7 +86,7 @@ const TagsSidebarBody = () => {
)} - diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx b/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx index 83b5cd4457..9b58b48539 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx @@ -1,3 +1,5 @@ +// @ts-check +import React from 'react'; import { Stack } from '@openedx/paragon'; import { useParams } from 'react-router-dom'; import { useIntl } from '@edx/frontend-platform/i18n'; @@ -13,7 +15,7 @@ const TagsSidebarHeader = () => { const { data: contentTaxonomyTagsCount, isSuccess: isContentTaxonomyTagsCountLoaded, - } = useContentTaxonomyTagsCount(contentId); + } = useContentTaxonomyTagsCount(contentId || ''); return ( diff --git a/src/content-tags-drawer/tags-sidebar/TagsTree.jsx b/src/content-tags-drawer/tags-sidebar/TagsTree.jsx index 9647ff949a..df9923271a 100644 --- a/src/content-tags-drawer/tags-sidebar/TagsTree.jsx +++ b/src/content-tags-drawer/tags-sidebar/TagsTree.jsx @@ -1,3 +1,5 @@ +// @ts-check +import React from 'react'; import PropTypes from 'prop-types'; import { Icon } from '@openedx/paragon'; import { Tag } from '@openedx/paragon/icons'; From 7a70d81fbfb2320ad32acd8f80c571375a55ce47 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Mon, 4 Mar 2024 17:24:44 -0500 Subject: [PATCH 12/17] style: Comments --- src/content-tags-drawer/ContentTagsDrawer.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/content-tags-drawer/ContentTagsDrawer.jsx b/src/content-tags-drawer/ContentTagsDrawer.jsx index c117f6fd29..31d15454c5 100644 --- a/src/content-tags-drawer/ContentTagsDrawer.jsx +++ b/src/content-tags-drawer/ContentTagsDrawer.jsx @@ -26,15 +26,18 @@ import Loading from '../generic/Loading'; * It is used both in interfaces of this MFE and in edx-platform interfaces such as iframe. * - If you want to use it as an iframe, the component obtains the `contentId` from the url parameters. * Functions to close the drawer are handled internally. + * TODO: We can delete this method when is no longer used on edx-platform. * - If you want to use it as react component, you need to pass the content id and the close functions * through the component parameters. */ const ContentTagsDrawer = ({ id, onClose }) => { const intl = useIntl(); + // TODO: We can delete this when the iframe is no longer used on edx-platform const params = useParams(); let contentId = id; if (contentId === undefined) { + // TODO: We can delete this when the iframe is no longer used on edx-platform contentId = params.contentId; } From bc39fe9f918ef5696662354f81420279164e6a7d Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Mon, 11 Mar 2024 15:27:46 -0500 Subject: [PATCH 13/17] refactor: Transform Sidebar to a wrapper component --- src/content-tags-drawer/index.scss | 2 +- .../TagsSidebarBody.jsx | 0 .../TagsSidebarBody.test.jsx | 0 .../TagsSidebarControls.scss} | 0 .../TagsSidebarHeader.jsx | 0 .../TagsSidebarHeader.test.jsx | 0 .../TagsTree.jsx | 0 .../TagsTree.test.jsx | 0 .../tags-sidebar-controls/index.jsx | 13 ++ src/content-tags-drawer/tags-sidebar/index.js | 2 - src/course-unit/CourseUnit.jsx | 15 +- src/course-unit/sidebar/LocationInfo.jsx | 38 +++++ src/course-unit/sidebar/PublishControls.jsx | 91 +++++++++++ src/course-unit/sidebar/index.jsx | 152 ++---------------- 14 files changed, 171 insertions(+), 142 deletions(-) rename src/content-tags-drawer/{tags-sidebar => tags-sidebar-controls}/TagsSidebarBody.jsx (100%) rename src/content-tags-drawer/{tags-sidebar => tags-sidebar-controls}/TagsSidebarBody.test.jsx (100%) rename src/content-tags-drawer/{tags-sidebar/TagsSidebar.scss => tags-sidebar-controls/TagsSidebarControls.scss} (100%) rename src/content-tags-drawer/{tags-sidebar => tags-sidebar-controls}/TagsSidebarHeader.jsx (100%) rename src/content-tags-drawer/{tags-sidebar => tags-sidebar-controls}/TagsSidebarHeader.test.jsx (100%) rename src/content-tags-drawer/{tags-sidebar => tags-sidebar-controls}/TagsTree.jsx (100%) rename src/content-tags-drawer/{tags-sidebar => tags-sidebar-controls}/TagsTree.test.jsx (100%) create mode 100644 src/content-tags-drawer/tags-sidebar-controls/index.jsx delete mode 100644 src/content-tags-drawer/tags-sidebar/index.js create mode 100644 src/course-unit/sidebar/LocationInfo.jsx create mode 100644 src/course-unit/sidebar/PublishControls.jsx diff --git a/src/content-tags-drawer/index.scss b/src/content-tags-drawer/index.scss index 829a0f18ae..d179bf86ab 100644 --- a/src/content-tags-drawer/index.scss +++ b/src/content-tags-drawer/index.scss @@ -1,2 +1,2 @@ @import "content-tags-drawer/TagBubble"; -@import "content-tags-drawer/tags-sidebar/TagsSidebar"; +@import "content-tags-drawer/tags-sidebar-controls/TagsSidebarControls"; diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx b/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarBody.jsx similarity index 100% rename from src/content-tags-drawer/tags-sidebar/TagsSidebarBody.jsx rename to src/content-tags-drawer/tags-sidebar-controls/TagsSidebarBody.jsx diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarBody.test.jsx b/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarBody.test.jsx similarity index 100% rename from src/content-tags-drawer/tags-sidebar/TagsSidebarBody.test.jsx rename to src/content-tags-drawer/tags-sidebar-controls/TagsSidebarBody.test.jsx diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebar.scss b/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarControls.scss similarity index 100% rename from src/content-tags-drawer/tags-sidebar/TagsSidebar.scss rename to src/content-tags-drawer/tags-sidebar-controls/TagsSidebarControls.scss diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx b/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarHeader.jsx similarity index 100% rename from src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.jsx rename to src/content-tags-drawer/tags-sidebar-controls/TagsSidebarHeader.jsx diff --git a/src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.test.jsx b/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarHeader.test.jsx similarity index 100% rename from src/content-tags-drawer/tags-sidebar/TagsSidebarHeader.test.jsx rename to src/content-tags-drawer/tags-sidebar-controls/TagsSidebarHeader.test.jsx diff --git a/src/content-tags-drawer/tags-sidebar/TagsTree.jsx b/src/content-tags-drawer/tags-sidebar-controls/TagsTree.jsx similarity index 100% rename from src/content-tags-drawer/tags-sidebar/TagsTree.jsx rename to src/content-tags-drawer/tags-sidebar-controls/TagsTree.jsx diff --git a/src/content-tags-drawer/tags-sidebar/TagsTree.test.jsx b/src/content-tags-drawer/tags-sidebar-controls/TagsTree.test.jsx similarity index 100% rename from src/content-tags-drawer/tags-sidebar/TagsTree.test.jsx rename to src/content-tags-drawer/tags-sidebar-controls/TagsTree.test.jsx diff --git a/src/content-tags-drawer/tags-sidebar-controls/index.jsx b/src/content-tags-drawer/tags-sidebar-controls/index.jsx new file mode 100644 index 0000000000..98ffc5e7c4 --- /dev/null +++ b/src/content-tags-drawer/tags-sidebar-controls/index.jsx @@ -0,0 +1,13 @@ +import TagsSidebarHeader from './TagsSidebarHeader'; +import TagsSidebarBody from './TagsSidebarBody'; + +const TagsSidebarControls = () => ( + <> + + + +); + +TagsSidebarControls.propTypes = {}; + +export default TagsSidebarControls; diff --git a/src/content-tags-drawer/tags-sidebar/index.js b/src/content-tags-drawer/tags-sidebar/index.js deleted file mode 100644 index a9f6c1af2c..0000000000 --- a/src/content-tags-drawer/tags-sidebar/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/prefer-default-export -export { default as TagsSidebarBody } from './TagsSidebarBody'; diff --git a/src/course-unit/CourseUnit.jsx b/src/course-unit/CourseUnit.jsx index 431d729ab1..5fa68e3018 100644 --- a/src/course-unit/CourseUnit.jsx +++ b/src/course-unit/CourseUnit.jsx @@ -23,6 +23,9 @@ import Sequence from './course-sequence'; import Sidebar from './sidebar'; import { useCourseUnit } from './hooks'; import messages from './messages'; +import PublishControls from './sidebar/PublishControls'; +import LocationInfo from './sidebar/LocationInfo'; +import TagsSidebarControls from '../content-tags-drawer/tags-sidebar-controls'; const CourseUnit = ({ courseId }) => { const { blockId } = useParams(); @@ -123,9 +126,15 @@ const CourseUnit = ({ courseId }) => { - - - + + + + + + + + + diff --git a/src/course-unit/sidebar/LocationInfo.jsx b/src/course-unit/sidebar/LocationInfo.jsx new file mode 100644 index 0000000000..1d63180883 --- /dev/null +++ b/src/course-unit/sidebar/LocationInfo.jsx @@ -0,0 +1,38 @@ +import { useSelector } from 'react-redux'; +import useCourseUnitData from './hooks'; +import { getCourseUnitData } from '../data/selectors'; +import { SidebarBody, SidebarFooter, SidebarHeader } from './components'; + +const LocationInfo = () => { + const { + title, + locationId, + releaseLabel, + visibilityState, + visibleToStaffOnly, + } = useCourseUnitData(useSelector(getCourseUnitData)); + + return ( + <> + + + + + ); +}; + +LocationInfo.propTypes = {}; + +export default LocationInfo; diff --git a/src/course-unit/sidebar/PublishControls.jsx b/src/course-unit/sidebar/PublishControls.jsx new file mode 100644 index 0000000000..786bc99038 --- /dev/null +++ b/src/course-unit/sidebar/PublishControls.jsx @@ -0,0 +1,91 @@ +import PropTypes from 'prop-types'; +import { useDispatch, useSelector } from 'react-redux'; +import { useToggle } from '@openedx/paragon'; +import { InfoOutline as InfoOutlineIcon } from '@openedx/paragon/icons'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import useCourseUnitData from './hooks'; +import { editCourseUnitVisibilityAndData } from '../data/thunk'; +import { SidebarBody, SidebarFooter, SidebarHeader } from './components'; +import { PUBLISH_TYPES } from '../constants'; +import { getCourseUnitData } from '../data/selectors'; +import messages from './messages'; +import ModalNotification from '../../generic/modal-notification'; + +const PublishControls = ({ blockId }) => { + const { + title, + locationId, + releaseLabel, + visibilityState, + visibleToStaffOnly, + } = useCourseUnitData(useSelector(getCourseUnitData)); + const intl = useIntl(); + + const [isDiscardModalOpen, openDiscardModal, closeDiscardModal] = useToggle(false); + const [isVisibleModalOpen, openVisibleModal, closeVisibleModal] = useToggle(false); + + const dispatch = useDispatch(); + + const handleCourseUnitVisibility = () => { + closeVisibleModal(); + dispatch(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.republish, null)); + }; + + const handleCourseUnitDiscardChanges = () => { + closeDiscardModal(); + dispatch(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.discardChanges)); + }; + + const handleCourseUnitPublish = () => { + dispatch(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic)); + }; + + return ( + <> + + + + + + + ); +}; + +PublishControls.propTypes = { + blockId: PropTypes.string, +}; + +PublishControls.defaultProps = { + blockId: null, +}; + +export default PublishControls; diff --git a/src/course-unit/sidebar/index.jsx b/src/course-unit/sidebar/index.jsx index d22adf00c6..99e29b0c14 100644 --- a/src/course-unit/sidebar/index.jsx +++ b/src/course-unit/sidebar/index.jsx @@ -1,146 +1,26 @@ import PropTypes from 'prop-types'; -import { useDispatch, useSelector } from 'react-redux'; import classNames from 'classnames'; -import { Card, useToggle } from '@openedx/paragon'; -import { InfoOutline as InfoOutlineIcon } from '@openedx/paragon/icons'; -import { useIntl } from '@edx/frontend-platform/i18n'; - -import ModalNotification from '../../generic/modal-notification'; -import { editCourseUnitVisibilityAndData } from '../data/thunk'; -import { getCourseUnitData } from '../data/selectors'; -import { PUBLISH_TYPES } from '../constants'; -import { SidebarBody, SidebarFooter, SidebarHeader } from './components'; -import useCourseUnitData from './hooks'; -import { TagsSidebarBody } from '../../content-tags-drawer/tags-sidebar'; -import TagsSidebarHeader from '../../content-tags-drawer/tags-sidebar/TagsSidebarHeader'; -import messages from './messages'; - -const Sidebar = ({ variant, blockId, ...props }) => { - const { - title, - locationId, - releaseLabel, - visibilityState, - visibleToStaffOnly, - } = useCourseUnitData(useSelector(getCourseUnitData)); - const intl = useIntl(); - const dispatch = useDispatch(); - const [isDiscardModalOpen, openDiscardModal, closeDiscardModal] = useToggle(false); - const [isVisibleModalOpen, openVisibleModal, closeVisibleModal] = useToggle(false); - - const handleCourseUnitVisibility = () => { - closeVisibleModal(); - dispatch(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.republish, null)); - }; - - const handleCourseUnitDiscardChanges = () => { - closeDiscardModal(); - dispatch(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.discardChanges)); - }; - - const handleCourseUnitPublish = () => { - dispatch(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic)); - }; - - let sidebarHeader; - let sidebarBody; - let hideFooter = false; - let className = ''; - switch (variant) { - case 'publish': - sidebarHeader = ( - - ); - sidebarBody = ( - - ); - break; - case 'location': - sidebarHeader = ( - - ); - sidebarBody = ( - - ); - break; - case 'tags': - sidebarHeader = ( - - ); - sidebarBody = ( - - ); - hideFooter = true; - className = 'tags-sidebar'; - break; - default: - break; - } - - return ( - - { sidebarHeader } - { sidebarBody } - { !hideFooter - && ( - - )} - - - - ); -}; +import { Card } from '@openedx/paragon'; + +const Sidebar = ({ className, children, ...props }) => ( + + {children} + +); Sidebar.propTypes = { - blockId: PropTypes.string, - variant: PropTypes.string.isRequired, + className: PropTypes.string, + children: PropTypes.node, }; Sidebar.defaultProps = { - blockId: null, + className: null, + children: null, }; export default Sidebar; From 1f33ca4a6a2efce18c24081e6fe2b395ab06b2e5 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Tue, 12 Mar 2024 10:53:09 -0500 Subject: [PATCH 14/17] fix: Issue with classnames --- src/course-unit/sidebar/PublishControls.jsx | 1 + src/course-unit/sidebar/Sidebar.scss | 6 +++--- .../sidebar/components/SidebarBody.jsx | 15 +++++++++++++-- src/course-unit/sidebar/index.jsx | 4 +--- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/course-unit/sidebar/PublishControls.jsx b/src/course-unit/sidebar/PublishControls.jsx index 786bc99038..424594f35b 100644 --- a/src/course-unit/sidebar/PublishControls.jsx +++ b/src/course-unit/sidebar/PublishControls.jsx @@ -48,6 +48,7 @@ const PublishControls = ({ blockId }) => { /> { +const SidebarBody = ({ + releaseLabel, + displayUnitLocation, + locationId, + visibleToStaffOnly, +}) => { const intl = useIntl(); const { editedOn, @@ -19,7 +25,10 @@ const SidebarBody = ({ releaseLabel, displayUnitLocation, locationId }) => { } = useSelector(getCourseUnitData); return ( - + {displayUnitLocation ? ( @@ -55,11 +64,13 @@ SidebarBody.propTypes = { releaseLabel: PropTypes.string.isRequired, displayUnitLocation: PropTypes.bool, locationId: PropTypes.string, + visibleToStaffOnly: PropTypes.bool, }; SidebarBody.defaultProps = { displayUnitLocation: false, locationId: null, + visibleToStaffOnly: false, }; export default SidebarBody; diff --git a/src/course-unit/sidebar/index.jsx b/src/course-unit/sidebar/index.jsx index 99e29b0c14..a7697c8abd 100644 --- a/src/course-unit/sidebar/index.jsx +++ b/src/course-unit/sidebar/index.jsx @@ -4,9 +4,7 @@ import { Card } from '@openedx/paragon'; const Sidebar = ({ className, children, ...props }) => ( {children} From 040e55bb82f121fd2e55590f35674d4667f078fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Ch=C3=A1vez?= Date: Wed, 13 Mar 2024 07:33:20 -0500 Subject: [PATCH 15/17] style: Typo on api.test.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: RĂ´mulo Penido --- src/content-tags-drawer/data/api.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content-tags-drawer/data/api.test.js b/src/content-tags-drawer/data/api.test.js index c092fb0d23..7ccb353548 100644 --- a/src/content-tags-drawer/data/api.test.js +++ b/src/content-tags-drawer/data/api.test.js @@ -100,7 +100,7 @@ describe('content tags drawer api calls', () => { expect(result).toEqual(contentTaxonomyTagsCountMock[contentId]); }); - it('should get content taxonomy tags count as cero', async () => { + it('should get content taxonomy tags count as zero', async () => { const contentId = 'block-v1:SampleTaxonomyOrg1+STC1+2023_1+type@vertical+block@aaf8b8eb86b54281aeeab12499d2cb0b'; axiosMock.onGet(getContentTaxonomyTagsCountApiUrl(contentId)).reply(200, {}); const result = await getContentTaxonomyTagsCount(contentId); From 94d3f1fe28448d79d664b3dd596e4d6ec826db77 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Wed, 13 Mar 2024 09:36:40 -0500 Subject: [PATCH 16/17] style: Update TagsSidebar styles --- .../tags-sidebar-controls/TagsSidebarBody.jsx | 6 ++++-- .../tags-sidebar-controls/TagsSidebarControls.scss | 8 -------- .../tags-sidebar-controls/TagsSidebarHeader.jsx | 5 ++++- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarBody.jsx b/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarBody.jsx index 9304fbf503..29b2e244d6 100644 --- a/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarBody.jsx +++ b/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarBody.jsx @@ -60,7 +60,9 @@ const TagsSidebarBody = () => { return ( <> - + { isContentTaxonomyTagsLoaded ? ( @@ -68,7 +70,7 @@ const TagsSidebarBody = () => { {tree.map((taxonomy) => (
} diff --git a/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarControls.scss b/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarControls.scss index a3ab86fd84..a3c0978f8c 100644 --- a/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarControls.scss +++ b/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarControls.scss @@ -1,14 +1,6 @@ .tags-sidebar { - .course-unit-sidebar-header { - padding-bottom: .25rem !important; - } - .tags-sidebar-body { - padding-left: .7rem !important; - .tags-sidebar-taxonomy { - border: none; - .collapsible-trigger { font-weight: bold; border: none; diff --git a/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarHeader.jsx b/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarHeader.jsx index 9b58b48539..e3927deb89 100644 --- a/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarHeader.jsx +++ b/src/content-tags-drawer/tags-sidebar-controls/TagsSidebarHeader.jsx @@ -18,7 +18,10 @@ const TagsSidebarHeader = () => { } = useContentTaxonomyTagsCount(contentId || ''); return ( - +

{intl.formatMessage(messages.tagsSidebarTitle)}

From c9261e49d8bcbf62675af3419b30309221b6bdf8 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 14 Mar 2024 08:46:07 -0500 Subject: [PATCH 17/17] feat: add description to comments --- src/content-tags-drawer/messages.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/content-tags-drawer/messages.js b/src/content-tags-drawer/messages.js index 7a597a76df..47a8c1bc86 100644 --- a/src/content-tags-drawer/messages.js +++ b/src/content-tags-drawer/messages.js @@ -36,10 +36,12 @@ const messages = defineMessages({ manageTagsButton: { id: 'course-authoring.content-tags-drawer.button.manage', defaultMessage: 'Manage Tags', + description: 'Label in the button that opens the drawer to edit content tags', }, tagsSidebarTitle: { id: 'course-authoring.course-unit.sidebar.tags.title', defaultMessage: 'Unit Tags', + description: 'Title of the tags sidebar', }, collapsibleAddTagsPlaceholderText: { id: 'course-authoring.content-tags-drawer.content-tags-collapsible.custom-menu.placeholder-text',