Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: redirect to unit page if the hit or its parent is a unit (TEMP) #35

349 changes: 5 additions & 344 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"@edx/frontend-component-footer": "^13.0.2",
"@edx/frontend-component-header": "^5.1.0",
"@edx/frontend-enterprise-hotjar": "^2.0.0",
"@edx/frontend-lib-content-components": "^2.1.5",
"@edx/frontend-lib-content-components": "^2.1.7",
"@edx/frontend-platform": "7.0.1",
"@edx/openedx-atlas": "^0.6.0",
"@fortawesome/fontawesome-svg-core": "1.2.36",
Expand Down Expand Up @@ -73,16 +73,15 @@
"email-validator": "2.0.4",
"file-saver": "^2.0.5",
"formik": "2.2.6",
"instantsearch.css": "^8.1.0",
"jszip": "^3.10.1",
"lodash": "4.17.21",
"meilisearch": "^0.38.0",
"moment": "2.29.4",
"prop-types": "15.7.2",
"react": "17.0.2",
"react-datepicker": "^4.13.0",
"react-dom": "17.0.2",
"react-helmet": "^6.1.0",
"react-instantsearch": "^7.7.1",
"react-redux": "7.2.9",
"react-responsive": "9.0.2",
"react-router": "6.16.0",
Expand Down
5 changes: 5 additions & 0 deletions src/CourseAuthoringRoutes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import CourseExportPage from './export-page/CourseExportPage';
import CourseImportPage from './import-page/CourseImportPage';
import { DECODED_ROUTES } from './constants';
import CourseChecklist from './course-checklist';
import GroupConfigurations from './group-configurations';

/**
* As of this writing, these routes are mounted at a path prefixed with the following:
Expand Down Expand Up @@ -100,6 +101,10 @@ const CourseAuthoringRoutes = () => {
path="course_team"
element={<PageWrap><CourseTeam courseId={courseId} /></PageWrap>}
/>
<Route
path="group_configurations"
element={<PageWrap><GroupConfigurations courseId={courseId} /></PageWrap>}
/>
<Route
path="settings/advanced"
element={<PageWrap><AdvancedSettings courseId={courseId} /></PageWrap>}
Expand Down
7 changes: 7 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,10 @@ export const DECODED_ROUTES = {
'/container/:blockId',
],
};

export const COURSE_BLOCK_NAMES = ({
chapter: { id: 'chapter', name: 'Section' },
sequential: { id: 'sequential', name: 'Subsection' },
vertical: { id: 'vertical', name: 'Unit' },
component: { id: 'component', name: 'Component' },
});
32 changes: 31 additions & 1 deletion src/content-tags-drawer/ContentTagsDrawer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,36 @@ const ContentTagsDrawer = ({ id, onClose }) => {
}, []);

const taxonomies = useMemo(() => {
const sortTaxonomies = (taxonomiesList) => {
const taxonomiesWithData = taxonomiesList.filter(
(t) => t.contentTags.length !== 0,
);

// Count implicit tags per taxonomy.
// TODO This count is also calculated individually
// in ContentTagsCollapsible. It should only be calculated once.
const tagsCountBytaxonomy = {};
taxonomiesWithData.forEach((tax) => {
tagsCountBytaxonomy[tax.id] = new Set(
tax.contentTags.flatMap(item => item.lineage),
).size;
});

// Sort taxonomies with data by implicit count
const sortedTaxonomiesWithData = taxonomiesWithData.sort(
(a, b) => tagsCountBytaxonomy[b.id] - tagsCountBytaxonomy[a.id],
);

// Empty taxonomies sorted by name.
// Since the query returns sorted by name,
// it is not necessary to do another sorting here.
const emptyTaxonomies = taxonomiesList.filter(
(t) => t.contentTags.length === 0,
);

return [...sortedTaxonomiesWithData, ...emptyTaxonomies];
};

if (taxonomyListData && contentTaxonomyTagsData) {
// Initialize list of content tags in taxonomies to populate
const taxonomiesList = taxonomyListData.results.map((taxonomy) => ({
Expand All @@ -125,7 +155,7 @@ const ContentTagsDrawer = ({ id, onClose }) => {
}
});

return taxonomiesList;
return sortTaxonomies(taxonomiesList);
}
return [];
}, [taxonomyListData, contentTaxonomyTagsData]);
Expand Down
95 changes: 89 additions & 6 deletions src/content-tags-drawer/ContentTagsDrawer.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,78 @@ describe('<ContentTagsDrawer />', () => {
},
],
},
{
name: 'Taxonomy 2',
taxonomyId: 124,
canTagObject: true,
tags: [
{
value: 'Tag 1',
lineage: ['Tag 1'],
canDeleteObjecttag: true,
},
],
},
{
name: 'Taxonomy 3',
taxonomyId: 125,
canTagObject: true,
tags: [
{
value: 'Tag 1.1.1',
lineage: ['Tag 1', 'Tag 1.1', 'Tag 1.1.1'],
canDeleteObjecttag: true,
},
],
},
{
name: '(B) Taxonomy 4',
taxonomyId: 126,
canTagObject: true,
tags: [],
},
{
name: '(A) Taxonomy 5',
taxonomyId: 127,
canTagObject: true,
tags: [],
},
],
},
});
getTaxonomyListData.mockResolvedValue({
results: [{
id: 123,
name: 'Taxonomy 1',
description: 'This is a description 1',
canTagObject: true,
}],
results: [
{
id: 123,
name: 'Taxonomy 1',
description: 'This is a description 1',
canTagObject: true,
},
{
id: 124,
name: 'Taxonomy 2',
description: 'This is a description 2',
canTagObject: true,
},
{
id: 125,
name: 'Taxonomy 3',
description: 'This is a description 3',
canTagObject: true,
},
{
id: 127,
name: '(A) Taxonomy 5',
description: 'This is a description 5',
canTagObject: true,
},
{
id: 126,
name: '(B) Taxonomy 4',
description: 'This is a description 4',
canTagObject: true,
},
],
});

useTaxonomyTagsData.mockReturnValue({
Expand Down Expand Up @@ -388,4 +450,25 @@ describe('<ContentTagsDrawer />', () => {

postMessageSpy.mockRestore();
});

it('should taxonomies must be ordered', async () => {
setupMockDataForStagedTagsTesting();
render(<RootWrapper />);
expect(await screen.findByText('Taxonomy 1')).toBeInTheDocument();

// First, taxonomies with content sorted by count implicit
// Later, empty taxonomies sorted by name
const expectedOrder = [
'Taxonomy 3', // 3 tags
'Taxonomy 1', // 2 tags
'Taxonomy 2', // 1 tag
'(A) Taxonomy 5',
'(B) Taxonomy 4',
];

const taxonomies = screen.getAllByText(/.*Taxonomy.*/);
for (let i = 0; i !== taxonomies.length; i++) {
expect(taxonomies[i].textContent).toBe(expectedOrder[i]);
}
});
});
12 changes: 8 additions & 4 deletions src/course-outline/CourseOutline.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ import SubHeader from '../generic/sub-header/SubHeader';
import ProcessingNotification from '../generic/processing-notification';
import InternetConnectionAlert from '../generic/internet-connection-alert';
import DeleteModal from '../generic/delete-modal/DeleteModal';
import ConfigureModal from '../generic/configure-modal/ConfigureModal';
import AlertMessage from '../generic/alert-message';
import getPageHeadTitle from '../generic/utils';
import { getCurrentItem } from './data/selectors';
import { getCurrentItem, getProctoredExamsFlag } from './data/selectors';
import { COURSE_BLOCK_NAMES } from './constants';
import HeaderNavigations from './header-navigations/HeaderNavigations';
import OutlineSideBar from './outline-sidebar/OutlineSidebar';
Expand All @@ -43,7 +44,6 @@ import UnitCard from './unit-card/UnitCard';
import HighlightsModal from './highlights-modal/HighlightsModal';
import EmptyPlaceholder from './empty-placeholder/EmptyPlaceholder';
import PublishModal from './publish-modal/PublishModal';
import ConfigureModal from './configure-modal/ConfigureModal';
import PageAlerts from './page-alerts/PageAlerts';
import DraggableList from './drag-helper/DraggableList';
import {
Expand Down Expand Up @@ -129,8 +129,10 @@ const CourseOutline = ({ courseId }) => {
title: processingNotificationTitle,
} = useSelector(getProcessingNotification);

const { category } = useSelector(getCurrentItem);
const deleteCategory = COURSE_BLOCK_NAMES[category]?.name.toLowerCase();
const currentItemData = useSelector(getCurrentItem);
const deleteCategory = COURSE_BLOCK_NAMES[currentItemData.category]?.name.toLowerCase();

const enableProctoredExams = useSelector(getProctoredExamsFlag);

/**
* Move section to new index
Expand Down Expand Up @@ -431,6 +433,8 @@ const CourseOutline = ({ courseId }) => {
isOpen={isConfigureModalOpen}
onClose={handleConfigureModalClose}
onConfigureSubmit={handleConfigureItemSubmit}
currentItemData={currentItemData}
enableProctoredExams={enableProctoredExams}
/>
<DeleteModal
category={deleteCategory}
Expand Down
1 change: 0 additions & 1 deletion src/course-outline/CourseOutline.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
@import "./empty-placeholder/EmptyPlaceholder";
@import "./highlights-modal/HighlightsModal";
@import "./publish-modal/PublishModal";
@import "./configure-modal/ConfigureModal";
@import "./drag-helper/SortableItem";
@import "./xblock-status/XBlockStatus";
@import "./paste-button/PasteButton";
Expand Down
5 changes: 3 additions & 2 deletions src/course-outline/CourseOutline.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ import {
import { executeThunk } from '../utils';
import { COURSE_BLOCK_NAMES, VIDEO_SHARING_OPTIONS } from './constants';
import CourseOutline from './CourseOutline';
import messages from './messages';

import configureModalMessages from '../generic/configure-modal/messages';
import headerMessages from './header-navigations/messages';
import cardHeaderMessages from './card-header/messages';
import enableHighlightsModalMessages from './enable-highlights-modal/messages';
import statusBarMessages from './status-bar/messages';
import configureModalMessages from './configure-modal/messages';
import pasteButtonMessages from './paste-button/messages';
import subsectionMessages from './subsection-card/messages';
import pageAlertMessages from './page-alerts/messages';
Expand All @@ -55,6 +55,7 @@ import {
moveSubsection,
moveUnit,
} from './drag-helper/utils';
import messages from './messages';

let axiosMock;
let store;
Expand Down
8 changes: 7 additions & 1 deletion src/course-unit/CourseUnit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const CourseUnit = ({ courseId }) => {
handleTitleEdit,
handleInternetConnectionFailed,
handleCreateNewCourseXBlock,
handleConfigureSubmit,
courseVerticalChildren,
} = useCourseUnit({ courseId, blockId });

Expand Down Expand Up @@ -85,6 +86,7 @@ const CourseUnit = ({ courseId }) => {
isTitleEditFormOpen={isTitleEditFormOpen}
handleTitleEdit={handleTitleEdit}
handleTitleEditSubmit={handleTitleEditSubmit}
handleConfigureSubmit={handleConfigureSubmit}
/>
)}
breadcrumbs={(
Expand Down Expand Up @@ -119,16 +121,20 @@ const CourseUnit = ({ courseId }) => {
)}
<Stack gap={4} className="mb-4">
{courseVerticalChildren.children.map(({
name, blockId: id, blockType: type, shouldScroll,
name, blockId: id, blockType: type, shouldScroll, userPartitionInfo, validationMessages,
}) => (
<CourseXBlock
id={id}
key={id}
title={name}
type={type}
blockId={blockId}
validationMessages={validationMessages}
shouldScroll={shouldScroll}
unitXBlockActions={unitXBlockActions}
handleConfigureSubmit={handleConfigureSubmit}
data-testid="course-xblock"
userPartitionInfo={userPartitionInfo}
/>
))}
</Stack>
Expand Down
16 changes: 16 additions & 0 deletions src/course-unit/CourseUnit.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,19 @@
@import "./add-component/AddComponent";
@import "./course-xblock/CourseXBlock";
@import "./sidebar/Sidebar";
@import "./header-title/HeaderTitle";

div.xblock-highlight {
animation: 5s glow;
animation-timing-function: cubic-bezier(1, 0, .72, .04);
}

@keyframes glow {
0% {
box-shadow: 0 0 5px 5px $primary-500;
}

100% {
box-shadow: unset;
}
}
Loading