Skip to content

Commit

Permalink
feat: [FC-0070] rendering split test content in unit page
Browse files Browse the repository at this point in the history
  • Loading branch information
ihor-romaniuk committed Nov 8, 2024
1 parent 89a6473 commit 3b37da2
Show file tree
Hide file tree
Showing 18 changed files with 226 additions and 47 deletions.
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const COURSE_BLOCK_NAMES = ({
sequential: { id: 'sequential', name: 'Subsection' },
vertical: { id: 'vertical', name: 'Unit' },
libraryContent: { id: 'library_content', name: 'Library content' },
splitTest: { id: 'split_test', name: 'Split Test' },
component: { id: 'component', name: 'Component' },
});

Expand Down
9 changes: 8 additions & 1 deletion src/course-unit/CourseUnit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import TagsSidebarControls from '../content-tags-drawer/tags-sidebar-controls';
import { PasteNotificationAlert } from './clipboard';
import XBlockContainerIframe from './xblock-container-iframe';
import MoveModal from './move-modal';
import SplitTestSidebarInfo from 'CourseAuthoring/course-unit/sidebar/SplitTestSidebarInfo';

const CourseUnit = ({ courseId }) => {
const { blockId } = useParams();
Expand Down Expand Up @@ -72,6 +73,7 @@ const CourseUnit = ({ courseId }) => {

const isUnitVerticalType = unitCategory === COURSE_BLOCK_NAMES.vertical.id;
const isUnitLibraryType = unitCategory === COURSE_BLOCK_NAMES.libraryContent.id;
const isSplitTestType = unitCategory === COURSE_BLOCK_NAMES.splitTest.id;

const unitLayout = [{ span: 12 }, { span: 0 }];
const defaultLayout = {
Expand Down Expand Up @@ -161,7 +163,7 @@ const CourseUnit = ({ courseId }) => {
)}
headerActions={(
<HeaderNavigations
unitCategory={unitCategory}
category={unitCategory}
headerNavigationsActions={headerNavigationsActions}
/>
)}
Expand Down Expand Up @@ -229,6 +231,11 @@ const CourseUnit = ({ courseId }) => {
</Sidebar>
</>
)}
{isSplitTestType && (
<Sidebar data-testid="course-split-test-sidebar">
<SplitTestSidebarInfo />
</Sidebar>
)}
</Stack>
</Layout.Element>
</Layout>
Expand Down
1 change: 1 addition & 0 deletions src/course-unit/CourseUnit.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@import "./sidebar/Sidebar";
@import "./header-title/HeaderTitle";
@import "./move-modal";
@import "./xblock-container-iframe";

.course-unit {
min-width: 900px;
Expand Down
64 changes: 37 additions & 27 deletions src/course-unit/breadcrumbs/Breadcrumbs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,51 @@ const Breadcrumbs = ({ courseId, sequenceId }) => {
const intl = useIntl();
const { ancestorXblocks = [] } = useSelector(getCourseSectionVertical);

const hasChildWithUrl = (children = []) => !!children.filter((child) => child.url).length;

return (
<nav className="d-flex align-center mb-2.5">
<ol className="p-0 m-0 d-flex align-center">
<nav className="d-flex align-center">
<ol className="p-0 m-0 d-flex align-center flex-wrap">
{ancestorXblocks.map(({ children, title, isLast }) => (
<li
className="d-flex"
className="d-flex mb-2.5"
key={title}
>
<Dropdown>
<Dropdown.Toggle
id="breadcrumbs-dropdown-section"
variant="link"
className="p-0 text-primary small"
>
{hasChildWithUrl(children) ? (
<Dropdown>
<Dropdown.Toggle
id="breadcrumbs-dropdown-section"
variant="link"
className="p-0 text-primary small"
>
<span className="small text-gray-700">
{title}
</span>
<Icon
src={ArrowDropDownIcon}
className="text-primary ml-1"
/>
</Dropdown.Toggle>
<Dropdown.Menu>
{children.map(({ url, displayName }) => (
<Dropdown.Item
key={url}
href={adoptCourseSectionUrl({ url, courseId, sequenceId })}
className="small"
data-testid="breadcrumbs-section-dropdown-item"
>
{displayName}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
) : (
<span className="p-0 text-primary small btn btn-link text-decoration-none">
<span className="small text-gray-700">
{title}
</span>
<Icon
src={ArrowDropDownIcon}
className="text-primary ml-1"
/>
</Dropdown.Toggle>
<Dropdown.Menu>
{children.map(({ url, displayName }) => (
<Dropdown.Item
key={url}
href={adoptCourseSectionUrl({ url, courseId, sequenceId })}
className="small"
data-testid="breadcrumbs-section-dropdown-item"
>
{displayName}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
</span>
)}
{!isLast && (
<Icon
src={ChevronRightIcon}
Expand Down
2 changes: 1 addition & 1 deletion src/course-unit/breadcrumbs/Breadcrumbs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
background: transparent;
}

.sub-header-title .sub-header-breadcrumbs {
.sub-header-breadcrumbs {
.dropdown-toggle::after {
display: none;
}
Expand Down
2 changes: 2 additions & 0 deletions src/course-unit/data/thunk.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,8 @@ export function patchUnitItemQuery({
dispatch(hideProcessingNotification());
dispatch(updateCourseOutlineInfo({}));
dispatch(updateCourseOutlineInfoLoadingStatus({ status: RequestStatus.IN_PROGRESS }));
const courseUnit = await getCourseUnitData(currentParentLocator);
dispatch(fetchCourseItemSuccess(courseUnit));
callbackFn();
} catch (error) {
handleResponseErrors(error, dispatch, updateSavingStatus);
Expand Down
8 changes: 4 additions & 4 deletions src/course-unit/header-navigations/HeaderNavigations.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { Edit as EditIcon } from '@openedx/paragon/icons';
import { COURSE_BLOCK_NAMES } from '../../constants';
import messages from './messages';

const HeaderNavigations = ({ headerNavigationsActions, unitCategory }) => {
const HeaderNavigations = ({ headerNavigationsActions, category }) => {
const intl = useIntl();
const { handleViewLive, handlePreview, handleEdit } = headerNavigationsActions;

return (
<nav className="header-navigations ml-auto flex-shrink-0">
{unitCategory === COURSE_BLOCK_NAMES.vertical.id && (
{category === COURSE_BLOCK_NAMES.vertical.id && (
<>
<Button
variant="outline-primary"
Expand All @@ -28,7 +28,7 @@ const HeaderNavigations = ({ headerNavigationsActions, unitCategory }) => {
</Button>
</>
)}
{unitCategory === COURSE_BLOCK_NAMES.libraryContent.id && (
{[COURSE_BLOCK_NAMES.libraryContent.id, COURSE_BLOCK_NAMES.splitTest.id].includes(category) && (
<Button
iconBefore={EditIcon}
variant="outline-primary"
Expand All @@ -47,7 +47,7 @@ HeaderNavigations.propTypes = {
handlePreview: PropTypes.func.isRequired,
handleEdit: PropTypes.func.isRequired,
}).isRequired,
unitCategory: PropTypes.string.isRequired,
category: PropTypes.string.isRequired,
};

export default HeaderNavigations;
10 changes: 7 additions & 3 deletions src/course-unit/header-title/HeaderTitle.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ const HeaderTitle = ({
const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false);
const { selectedPartitionIndex, selectedGroupsLabel } = currentItemData.userPartitionInfo;

const isXBlockComponent = [
COURSE_BLOCK_NAMES.libraryContent.id,
COURSE_BLOCK_NAMES.splitTest.id,
COURSE_BLOCK_NAMES.component.id,
].includes(currentItemData.category);

const onConfigureSubmit = (...arg) => {
handleConfigureSubmit(currentItemData.id, ...arg, closeConfigureModal);
};
Expand Down Expand Up @@ -87,9 +93,7 @@ const HeaderTitle = ({
onConfigureSubmit={onConfigureSubmit}
currentItemData={currentItemData}
isSelfPaced={false}
isXBlockComponent={
[COURSE_BLOCK_NAMES.libraryContent.id, COURSE_BLOCK_NAMES.component.id].includes(currentItemData.category)
}
isXBlockComponent={isXBlockComponent}
/>
</div>
{getVisibilityMessage()}
Expand Down
15 changes: 15 additions & 0 deletions src/course-unit/sidebar/Sidebar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,19 @@
text-decoration: line-through;
}
}

.course-split-test-sidebar {
padding: $spacer;

@extend %base-font-params;

.course-split-test-sidebar-title {
font-size: $font-size-base;
line-height: $line-height-base;
}

.course-split-test-sidebar-devider {
width: 100%;
}
}
}
61 changes: 61 additions & 0 deletions src/course-unit/sidebar/SplitTestSidebarInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { Card, Stack } from '@openedx/paragon';
import { Link } from 'react-router-dom';
import { useIntl } from '@edx/frontend-platform/i18n';

import messages from './messages';

const SplitTestSidebarInfo = () => {
const intl = useIntl();
const boldTagWrapper = (chunks: React.ReactNode) => <strong>{chunks}</strong>;

return (
<Card.Body className="course-split-test-sidebar">
<Stack>
<h3 className="course-split-test-sidebar-title">
{intl.formatMessage(messages.sidebarSplitTestAddComponentTitle)}
</h3>
<p>
{intl.formatMessage(messages.sidebarSplitTestSelectComponentType, { bold_tag: boldTagWrapper })}
</p>
<p>
{intl.formatMessage(messages.sidebarSplitTestComponentAdded)}
</p>
<h3 className="course-split-test-sidebar-title">
{intl.formatMessage(messages.sidebarSplitTestEditComponentTitle)}
</h3>
<p>
{intl.formatMessage(messages.sidebarSplitTestEditComponentInstruction, { bold_tag: boldTagWrapper })}
</p>
<h3 className="course-split-test-sidebar-title">
{intl.formatMessage(messages.sidebarSplitTestReorganizeComponentTitle)}
</h3>
<p>
{intl.formatMessage(messages.sidebarSplitTestReorganizeComponentInstruction)}
</p>
<p>
{intl.formatMessage(messages.sidebarSplitTestReorganizeGroupsInstruction)}
</p>
<h3 className="course-split-test-sidebar-title">
{intl.formatMessage(messages.sidebarSplitTestExperimentComponentTitle)}
</h3>
<p className="mb-0">
{intl.formatMessage(messages.sidebarSplitTestExperimentComponentInstruction)}
</p>
<hr className="course-split-test-sidebar-devider my-4" />
<Link
to="https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_components.html#components-that-contain-other-components"
className="btn btn-outline-primary btn-sm"
target="_blank"
rel="noopener noreferrer"
>
{intl.formatMessage(messages.sidebarSplitTestLearnMoreLinkLabel)}
</Link>
</Stack>
</Card.Body>
);
};

SplitTestSidebarInfo.propTypes = {};

export default SplitTestSidebarInfo;
55 changes: 55 additions & 0 deletions src/course-unit/sidebar/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,61 @@ 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?',
},
sidebarSplitTestAddComponentTitle: {
id: 'course-authoring.course-unit.split-test.sidebar.add-component.title',
defaultMessage: 'Adding components',
description: 'Title for the section that explains how to add components to a split test',
},
sidebarSplitTestSelectComponentType: {
id: 'course-authoring.course-unit.split-test.sidebar.add-component.select-type',
defaultMessage: 'Select a component type under {bold_tag}Add New Component{bold_tag}. Then select a template.',
description: 'Instruction text for selecting a component type and template when adding new components',
},
sidebarSplitTestComponentAdded: {
id: 'course-authoring.course-unit.split-test.sidebar.add-component.component-added',
defaultMessage: 'The new component is added at the bottom of the page or group. You can then edit and move the component.',
description: 'Instruction text indicating that the component has been added and can be moved or edited',
},
sidebarSplitTestEditComponentTitle: {
id: 'course-authoring.course-unit.split-test.sidebar.edit-component.title',
defaultMessage: 'Editing components',
description: 'Title for the section that explains how to edit components in a split test',
},
sidebarSplitTestEditComponentInstruction: {
id: 'course-authoring.course-unit.split-test.sidebar.edit-component.instruction',
defaultMessage: 'Click the {bold_tag}Edit{bold_tag} icon in a component to edit its content.',
description: 'Instruction text for editing a component by clicking the edit icon',
},
sidebarSplitTestReorganizeComponentTitle: {
id: 'course-authoring.course-unit.split-test.sidebar.reorganize-component.title',
defaultMessage: 'Reorganizing components',
description: 'Title for the section that explains how to reorganize components within a split test',
},
sidebarSplitTestReorganizeComponentInstruction: {
id: 'course-authoring.course-unit.split-test.sidebar.reorganize-component.instruction',
defaultMessage: 'Drag components to new locations within this component.',
description: 'Instruction text for reorganizing components by dragging them to new locations within a split test',
},
sidebarSplitTestReorganizeGroupsInstruction: {
id: 'course-authoring.course-unit.split-test.sidebar.reorganize-component.drag-to-groups',
defaultMessage: 'For content experiments, you can drag components to other groups.',
description: 'Instruction text for dragging components to other groups for content experiments',
},
sidebarSplitTestExperimentComponentTitle: {
id: 'course-authoring.course-unit.split-test.sidebar.experiment-component.title',
defaultMessage: 'Working with content experiments',
description: 'Title for the section that explains how to work with content experiments',
},
sidebarSplitTestExperimentComponentInstruction: {
id: 'course-authoring.course-unit.split-test.sidebar.experiment-component.confirm-config',
defaultMessage: 'Confirm that you have properly configured content in each of your experiment groups.',
description: 'Instruction text reminding users to check content configuration in each experiment group',
},
sidebarSplitTestLearnMoreLinkLabel: {
id: 'course-authoring.course-unit.split-test.sidebar.learn-more-link.label',
defaultMessage: 'Learn more about component containers',
description: 'Text for a link that directs users to more information about component containers in the split test setup.',
},
});

export default messages;
4 changes: 4 additions & 0 deletions src/course-unit/xblock-container-iframe/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.xblock-container-iframe {
width: calc(100% + ($spacer * .3125));
margin: 0 (-($spacer * .3125));
}
3 changes: 2 additions & 1 deletion src/course-unit/xblock-container-iframe/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({ blockId }) => {
allow={IFRAME_FEATURE_POLICY}
allowFullScreen
loading="lazy"
style={{ width: '100%', height: iframeHeight + IFRAME_BOTTOM_OFFSET }}
style={{ height: iframeHeight + IFRAME_BOTTOM_OFFSET }}
scrolling="no"
referrerPolicy="origin"
className="xblock-container-iframe"
/>
);
};
Expand Down
4 changes: 3 additions & 1 deletion src/generic/configure-modal/ConfigureModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ const ConfigureModal = ({
break;
case COURSE_BLOCK_NAMES.vertical.id:
case COURSE_BLOCK_NAMES.libraryContent.id:
case COURSE_BLOCK_NAMES.splitTest.id:
case COURSE_BLOCK_NAMES.component.id:
// groupAccess should be {partitionId: [group1, group2]} or {} if selectedPartitionIndex === -1
if (data.selectedPartitionIndex >= 0) {
Expand Down Expand Up @@ -244,11 +245,12 @@ const ConfigureModal = ({
);
case COURSE_BLOCK_NAMES.vertical.id:
case COURSE_BLOCK_NAMES.libraryContent.id:
case COURSE_BLOCK_NAMES.splitTest.id:
case COURSE_BLOCK_NAMES.component.id:
return (
<UnitTab
isXBlockComponent={isXBlockComponent}
isLibraryContent={COURSE_BLOCK_NAMES.libraryContent.id === category}
category={category}
values={values}
setFieldValue={setFieldValue}
showWarning={visibilityState === VisibilityTypes.STAFF_ONLY && !ancestorHasStaffLock}
Expand Down
Loading

0 comments on commit 3b37da2

Please sign in to comment.