diff --git a/src/course-outline/CourseOutline.jsx b/src/course-outline/CourseOutline.jsx index 2d17118c4..b0c9844dd 100644 --- a/src/course-outline/CourseOutline.jsx +++ b/src/course-outline/CourseOutline.jsx @@ -211,6 +211,7 @@ const CourseOutline = ({ courseId }) => { background: 'white', padding: '1.75rem', marginBottom: '1.5rem', + borderRadius: '0.35rem', boxShadow: '0 0 .125rem rgba(0, 0, 0, .15), 0 0 .25rem rgba(0, 0, 0, .15)', }} > @@ -244,6 +245,7 @@ const CourseOutline = ({ courseId }) => { background: '#f8f7f6', padding: '1rem 1.5rem', marginBottom: '1.5rem', + borderRadius: '0.35rem', boxShadow: '0 0 .125rem rgba(0, 0, 0, .15), 0 0 .25rem rgba(0, 0, 0, .15)', }} > diff --git a/src/course-outline/card-header/messages.js b/src/course-outline/card-header/messages.js index 0197722dd..751ba4815 100644 --- a/src/course-outline/card-header/messages.js +++ b/src/course-outline/card-header/messages.js @@ -21,6 +21,10 @@ const messages = defineMessages({ id: 'course-authoring.course-outline.card.status-badge.draft', defaultMessage: 'Draft', }, + statusBadgeUnpublishedChanges: { + id: 'course-authoring.course-outline.card.status-badge.draft-unpublished-changes', + defaultMessage: 'Draft (Unpublished changes)', + }, altButtonEdit: { id: 'course-authoring.course-outline.card.button.edit.alt', defaultMessage: 'Edit', diff --git a/src/course-outline/constants.js b/src/course-outline/constants.js index bf3ec53ef..e14752726 100644 --- a/src/course-outline/constants.js +++ b/src/course-outline/constants.js @@ -1,12 +1,11 @@ export const ITEM_BADGE_STATUS = /** @type {const} */ ({ live: 'live', publishedNotLive: 'published_not_live', + unpublishedChanges: 'unpublished_changes', staffOnly: 'staff_only', draft: 'draft', }); -export const STAFF_ONLY = 'staff_only'; - export const HIGHLIGHTS_FIELD_MAX_LENGTH = 250; export const CHECKLIST_FILTERS = /** @type {const} */ ({ diff --git a/src/course-outline/section-card/SectionCard.jsx b/src/course-outline/section-card/SectionCard.jsx index 2498bb527..61f54f9b1 100644 --- a/src/course-outline/section-card/SectionCard.jsx +++ b/src/course-outline/section-card/SectionCard.jsx @@ -51,10 +51,7 @@ const SectionCard = ({ displayName, hasChanges, published, - releasedToStudents, - visibleToStaffOnly = false, visibilityState, - staffOnlyMessage, highlights, actions, isHeaderVisible = true, @@ -63,10 +60,8 @@ const SectionCard = ({ const sectionStatus = getItemStatus({ published, - releasedToStudents, - visibleToStaffOnly, visibilityState, - staffOnlyMessage, + hasChanges, }); const handleExpandContent = () => { @@ -110,7 +105,7 @@ const SectionCard = ({ > @@ -190,10 +185,7 @@ SectionCard.propTypes = { displayName: PropTypes.string.isRequired, published: PropTypes.bool.isRequired, hasChanges: PropTypes.bool.isRequired, - releasedToStudents: PropTypes.bool.isRequired, - visibleToStaffOnly: PropTypes.bool, visibilityState: PropTypes.string.isRequired, - staffOnlyMessage: PropTypes.bool.isRequired, highlights: PropTypes.arrayOf(PropTypes.string).isRequired, shouldScroll: PropTypes.bool, explanatoryMessage: PropTypes.string, diff --git a/src/course-outline/section-card/SectionCard.test.jsx b/src/course-outline/section-card/SectionCard.test.jsx index 41dfba995..05789f5f6 100644 --- a/src/course-outline/section-card/SectionCard.test.jsx +++ b/src/course-outline/section-card/SectionCard.test.jsx @@ -10,7 +10,6 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import initializeStore from '../../store'; import SectionCard from './SectionCard'; -import cardHeaderMessages from '../card-header/messages'; // eslint-disable-next-line no-unused-vars let axiosMock; @@ -20,10 +19,7 @@ const section = { id: '123', displayName: 'Section Name', published: true, - releasedToStudents: true, - visibleToStaffOnly: false, - visibilityState: 'visible', - staffOnlyMessage: false, + visibilityState: 'live', hasChanges: false, highlights: ['highlight 1', 'highlight 2'], actions: { @@ -111,53 +107,6 @@ describe('', () => { expect(onEditSectionSubmit).toHaveBeenCalled(); }); - it('renders live status', async () => { - const { findByText } = renderComponent(); - expect(await findByText(cardHeaderMessages.statusBadgeLive.defaultMessage)).toBeInTheDocument(); - }); - - it('renders published but live status', async () => { - const { findByText } = renderComponent({ - section: { - ...section, - published: true, - releasedToStudents: false, - visibleToStaffOnly: false, - visibilityState: 'visible', - staffOnlyMessage: false, - }, - }); - expect(await findByText(cardHeaderMessages.statusBadgePublishedNotLive.defaultMessage)).toBeInTheDocument(); - }); - - it('renders staff status', async () => { - const { findByText } = renderComponent({ - section: { - ...section, - published: false, - releasedToStudents: false, - visibleToStaffOnly: true, - visibilityState: 'staff_only', - staffOnlyMessage: true, - }, - }); - expect(await findByText(cardHeaderMessages.statusBadgeStaffOnly.defaultMessage)).toBeInTheDocument(); - }); - - it('renders draft status', async () => { - const { findByText } = renderComponent({ - section: { - ...section, - published: false, - releasedToStudents: false, - visibleToStaffOnly: false, - visibilityState: 'staff_only', - staffOnlyMessage: false, - }, - }); - expect(await findByText(cardHeaderMessages.statusBadgeDraft.defaultMessage)).toBeInTheDocument(); - }); - it('hides header based on isHeaderVisible flag', async () => { const { queryByTestId } = renderComponent({ section: { diff --git a/src/course-outline/subsection-card/SubsectionCard.jsx b/src/course-outline/subsection-card/SubsectionCard.jsx index 86dbab1aa..f6b0b943e 100644 --- a/src/course-outline/subsection-card/SubsectionCard.jsx +++ b/src/course-outline/subsection-card/SubsectionCard.jsx @@ -35,10 +35,7 @@ const SubsectionCard = ({ displayName, hasChanges, published, - releasedToStudents, - visibleToStaffOnly = false, visibilityState, - staffOnlyMessage, actions, isHeaderVisible = true, } = subsection; @@ -46,10 +43,8 @@ const SubsectionCard = ({ const [isExpanded, setIsExpanded] = useState(!isHeaderVisible); const subsectionStatus = getItemStatus({ published, - releasedToStudents, - visibleToStaffOnly, visibilityState, - staffOnlyMessage, + hasChanges, }); const handleExpandContent = () => { @@ -154,10 +149,7 @@ SubsectionCard.propTypes = { displayName: PropTypes.string.isRequired, published: PropTypes.bool.isRequired, hasChanges: PropTypes.bool.isRequired, - releasedToStudents: PropTypes.bool.isRequired, - visibleToStaffOnly: PropTypes.bool, visibilityState: PropTypes.string.isRequired, - staffOnlyMessage: PropTypes.bool.isRequired, shouldScroll: PropTypes.bool, }).isRequired, subsection: PropTypes.shape({ @@ -165,10 +157,7 @@ SubsectionCard.propTypes = { displayName: PropTypes.string.isRequired, published: PropTypes.bool.isRequired, hasChanges: PropTypes.bool.isRequired, - releasedToStudents: PropTypes.bool.isRequired, - visibleToStaffOnly: PropTypes.bool, visibilityState: PropTypes.string.isRequired, - staffOnlyMessage: PropTypes.bool.isRequired, shouldScroll: PropTypes.bool, actions: PropTypes.shape({ deletable: PropTypes.bool.isRequired, diff --git a/src/course-outline/subsection-card/SubsectionCard.test.jsx b/src/course-outline/subsection-card/SubsectionCard.test.jsx index 82d464e96..800cfafb8 100644 --- a/src/course-outline/subsection-card/SubsectionCard.test.jsx +++ b/src/course-outline/subsection-card/SubsectionCard.test.jsx @@ -10,6 +10,7 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import initializeStore from '../../store'; import SubsectionCard from './SubsectionCard'; +import cardHeaderMessages from '../card-header/messages'; // eslint-disable-next-line no-unused-vars let axiosMock; @@ -19,10 +20,7 @@ const section = { id: '123', displayName: 'Section Name', published: true, - releasedToStudents: true, - visibleToStaffOnly: false, - visibilityState: 'visible', - staffOnlyMessage: false, + visibilityState: 'live', hasChanges: false, highlights: ['highlight 1', 'highlight 2'], }; @@ -31,10 +29,7 @@ const subsection = { id: '123', displayName: 'Subsection Name', published: true, - releasedToStudents: true, - visibleToStaffOnly: false, - visibilityState: 'visible', - staffOnlyMessage: false, + visibilityState: 'live', hasChanges: false, actions: { draggable: true, @@ -161,4 +156,42 @@ describe('', () => { expect(within(element).queryByTestId('subsection-card-header__menu-delete-button')).not.toBeInTheDocument(); expect(queryByTestId('new-unit-button')).not.toBeInTheDocument(); }); + + it('renders live status', async () => { + const { findByText } = renderComponent(); + expect(await findByText(cardHeaderMessages.statusBadgeLive.defaultMessage)).toBeInTheDocument(); + }); + + it('renders published but live status', async () => { + const { findByText } = renderComponent({ + subsection: { + ...subsection, + published: true, + visibilityState: 'ready', + }, + }); + expect(await findByText(cardHeaderMessages.statusBadgePublishedNotLive.defaultMessage)).toBeInTheDocument(); + }); + + it('renders staff status', async () => { + const { findByText } = renderComponent({ + subsection: { + ...subsection, + published: false, + visibilityState: 'staff_only', + }, + }); + expect(await findByText(cardHeaderMessages.statusBadgeStaffOnly.defaultMessage)).toBeInTheDocument(); + }); + + it('renders draft status', async () => { + const { findByText } = renderComponent({ + subsection: { + ...subsection, + published: false, + visibilityState: 'needs_attention', + }, + }); + expect(await findByText(cardHeaderMessages.statusBadgeDraft.defaultMessage)).toBeInTheDocument(); + }); }); diff --git a/src/course-outline/unit-card/UnitCard.jsx b/src/course-outline/unit-card/UnitCard.jsx index 9a2c8e927..0abb1943d 100644 --- a/src/course-outline/unit-card/UnitCard.jsx +++ b/src/course-outline/unit-card/UnitCard.jsx @@ -31,20 +31,15 @@ const UnitCard = ({ displayName, hasChanges, published, - releasedToStudents, - visibleToStaffOnly = false, visibilityState, - staffOnlyMessage, actions, isHeaderVisible = true, } = unit; const unitStatus = getItemStatus({ published, - releasedToStudents, - visibleToStaffOnly, visibilityState, - staffOnlyMessage, + hasChanges, }); const handleClickMenuButton = () => { @@ -124,10 +119,7 @@ UnitCard.propTypes = { displayName: PropTypes.string.isRequired, published: PropTypes.bool.isRequired, hasChanges: PropTypes.bool.isRequired, - releasedToStudents: PropTypes.bool.isRequired, - visibleToStaffOnly: PropTypes.bool, visibilityState: PropTypes.string.isRequired, - staffOnlyMessage: PropTypes.bool.isRequired, shouldScroll: PropTypes.bool, actions: PropTypes.shape({ deletable: PropTypes.bool.isRequired, @@ -142,10 +134,7 @@ UnitCard.propTypes = { displayName: PropTypes.string.isRequired, published: PropTypes.bool.isRequired, hasChanges: PropTypes.bool.isRequired, - releasedToStudents: PropTypes.bool.isRequired, - visibleToStaffOnly: PropTypes.bool, visibilityState: PropTypes.string.isRequired, - staffOnlyMessage: PropTypes.bool.isRequired, shouldScroll: PropTypes.bool, }).isRequired, section: PropTypes.shape({ @@ -153,10 +142,7 @@ UnitCard.propTypes = { displayName: PropTypes.string.isRequired, published: PropTypes.bool.isRequired, hasChanges: PropTypes.bool.isRequired, - releasedToStudents: PropTypes.bool.isRequired, - visibleToStaffOnly: PropTypes.bool, visibilityState: PropTypes.string.isRequired, - staffOnlyMessage: PropTypes.bool.isRequired, shouldScroll: PropTypes.bool, }).isRequired, onOpenPublishModal: PropTypes.func.isRequired, diff --git a/src/course-outline/unit-card/UnitCard.test.jsx b/src/course-outline/unit-card/UnitCard.test.jsx index a18799009..8fc2dd321 100644 --- a/src/course-outline/unit-card/UnitCard.test.jsx +++ b/src/course-outline/unit-card/UnitCard.test.jsx @@ -19,10 +19,7 @@ const section = { id: '1', displayName: 'Section Name', published: true, - releasedToStudents: true, - visibleToStaffOnly: false, - visibilityState: 'visible', - staffOnlyMessage: false, + visibilityState: 'live', hasChanges: false, highlights: ['highlight 1', 'highlight 2'], }; @@ -31,10 +28,7 @@ const subsection = { id: '12', displayName: 'Subsection Name', published: true, - releasedToStudents: true, - visibleToStaffOnly: false, - visibilityState: 'visible', - staffOnlyMessage: false, + visibilityState: 'live', hasChanges: false, }; @@ -42,10 +36,7 @@ const unit = { id: '123', displayName: 'unit Name', published: true, - releasedToStudents: true, - visibleToStaffOnly: false, - visibilityState: 'visible', - staffOnlyMessage: false, + visibilityState: 'live', hasChanges: false, actions: { draggable: true, diff --git a/src/course-outline/utils.jsx b/src/course-outline/utils.jsx index 481dd1f72..2dccd59b3 100644 --- a/src/course-outline/utils.jsx +++ b/src/course-outline/utils.jsx @@ -4,35 +4,31 @@ import { EditOutline as EditOutlineIcon, } from '@edx/paragon/icons'; -import { ITEM_BADGE_STATUS, STAFF_ONLY, VIDEO_SHARING_OPTIONS } from './constants'; +import { ITEM_BADGE_STATUS, VIDEO_SHARING_OPTIONS } from './constants'; +import { VisibilityTypes } from '../data/constants'; /** * Get section status depended on section info * @param {bool} published - value from section info - * @param {bool} releasedToStudents - value from section info - * @param {bool} visibleToStaffOnly - value from section info * @param {string} visibilityState - value from section info - * @param {bool} staffOnlyMessage - value from section info * @returns {ITEM_BADGE_STATUS[keyof ITEM_BADGE_STATUS]} */ const getItemStatus = ({ published, - releasedToStudents, - visibleToStaffOnly, visibilityState, - staffOnlyMessage, + hasChanges, }) => { switch (true) { - case published && releasedToStudents: - return ITEM_BADGE_STATUS.live; - case published && !releasedToStudents: - return ITEM_BADGE_STATUS.publishedNotLive; - case visibleToStaffOnly && staffOnlyMessage && visibilityState === STAFF_ONLY: + case visibilityState === VisibilityTypes.STAFF_ONLY: return ITEM_BADGE_STATUS.staffOnly; - case !published: - return ITEM_BADGE_STATUS.draft; + case visibilityState === VisibilityTypes.LIVE: + return ITEM_BADGE_STATUS.live; + case published && !hasChanges: + return ITEM_BADGE_STATUS.publishedNotLive; + case published && hasChanges: + return ITEM_BADGE_STATUS.unpublishedChanges; default: - return ''; + return ITEM_BADGE_STATUS.draft; } }; @@ -61,6 +57,11 @@ const getItemStatusBadgeContent = (status, messages, intl) => { badgeTitle: intl.formatMessage(messages.statusBadgeStaffOnly), badgeIcon: LockIcon, }; + case ITEM_BADGE_STATUS.unpublishedChanges: + return { + badgeTitle: intl.formatMessage(messages.statusBadgeUnpublishedChanges), + badgeIcon: EditOutlineIcon, + }; case ITEM_BADGE_STATUS.draft: return { badgeTitle: intl.formatMessage(messages.statusBadgeDraft), diff --git a/src/data/constants.js b/src/data/constants.js index bd01f09dd..c025510b4 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -5,7 +5,7 @@ * @readonly * @enum {string} */ -export const RequestStatus = { +export const RequestStatus = /** @type {const} */ ({ IN_PROGRESS: 'in-progress', SUCCESSFUL: 'successful', FAILED: 'failed', @@ -14,36 +14,37 @@ export const RequestStatus = { CLEAR: 'clear', PARTIAL: 'partial', NOT_FOUND: 'not-found', -}; +}); /** * Team sizes enum * @enum * @type {{MIN: number, MAX: number, DEFAULT: number}} */ -export const TeamSizes = { +export const TeamSizes = /** @type {const} */ ({ DEFAULT: 5, MIN: 1, MAX: 500, -}; +}); /** * Group types enum * @enum * @type {{PRIVATE_MANAGED: string, PUBLIC_MANAGED: string, OPEN: string}} */ -export const GroupTypes = { +export const GroupTypes = /** @type {const} */ ({ OPEN: 'open', PUBLIC_MANAGED: 'public_managed', PRIVATE_MANAGED: 'private_managed', -}; +}); -export const DivisionSchemes = { +export const DivisionSchemes = /** @type {const} */ ({ NONE: 'none', COHORT: 'cohort', -}; +}); -export const VisibilityTypes = { +export const VisibilityTypes = /** @type {const} */ ({ + LIVE: 'live', STAFF_ONLY: 'staff_only', HIDE_AFTER_DUE: 'hide_after_due', -}; +});