Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ ENABLE_ASSETS_PAGE=false
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN=false
ENABLE_TAGGING_TAXONOMY_PAGES=true
ENABLE_CERTIFICATE_PAGE=true
ENABLE_COURSE_IMPORT_IN_LIBRARY=false
BBB_LEARN_MORE_URL=''
HOTJAR_APP_ID=''
HOTJAR_VERSION=6
Expand Down
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ ENABLE_UNIT_PAGE=false
ENABLE_ASSETS_PAGE=false
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN=true
ENABLE_CERTIFICATE_PAGE=true
ENABLE_COURSE_IMPORT_IN_LIBRARY=true
ENABLE_NEW_VIDEO_UPLOAD_PAGE=true
ENABLE_TAGGING_TAXONOMY_PAGES=true
BBB_LEARN_MORE_URL=''
Expand Down
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ ENABLE_UNIT_PAGE=true
ENABLE_ASSETS_PAGE=false
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN=true
ENABLE_CERTIFICATE_PAGE=true
ENABLE_COURSE_IMPORT_IN_LIBRARY=true
ENABLE_TAGGING_TAXONOMY_PAGES=true
BBB_LEARN_MORE_URL=''
INVITE_STUDENTS_EMAIL_TO="[email protected]"
Expand Down
18 changes: 16 additions & 2 deletions src/course-outline/data/api.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { XBlock } from '@src/data/types';
import { CourseOutline } from './types';
import { CourseOutline, CourseDetails } from './types';

const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;

export const getCourseOutlineIndexApiUrl = (
courseId: string,
) => `${getApiBaseUrl()}/api/contentstore/v1/course_index/${courseId}`;

export const getCourseDetailsApiUrl = (courseId) => `${getApiBaseUrl()}/api/contentstore/v1/course_details/${courseId}`;

export const getCourseBestPracticesApiUrl = ({
courseId,
excludeGraded,
Expand Down Expand Up @@ -46,7 +48,7 @@ export const createDiscussionsTopicsUrl = (courseId: string) => `${getApiBaseUrl
/**
* Get course outline index.
* @param {string} courseId
* @returns {Promise<courseOutline>}
* @returns {Promise<CourseOutline>}
*/
export async function getCourseOutlineIndex(courseId: string): Promise<CourseOutline> {
const { data } = await getAuthenticatedHttpClient()
Expand All @@ -55,6 +57,18 @@ export async function getCourseOutlineIndex(courseId: string): Promise<CourseOut
return camelCaseObject(data);
}

/**
* Get course details.
* @param {string} courseId
* @returns {Promise<CourseDetails>}
*/
export async function getCourseDetails(courseId: string): Promise<CourseDetails> {
const { data } = await getAuthenticatedHttpClient()
.get(getCourseDetailsApiUrl(courseId));

return camelCaseObject(data);
}

/**
*
* @param courseId
Expand Down
13 changes: 10 additions & 3 deletions src/course-outline/data/apiHooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { skipToken, useMutation, useQuery } from '@tanstack/react-query';
import { createCourseXblock } from '@src/course-unit/data/api';
import { getCourseItem } from './api';
import { getCourseDetails, getCourseItem } from './api';

export const courseOutlineQueryKeys = {
all: ['courseOutline'],
Expand All @@ -9,7 +9,7 @@ export const courseOutlineQueryKeys = {
*/
contentLibrary: (courseId?: string) => [...courseOutlineQueryKeys.all, courseId],
courseItemId: (itemId?: string) => [...courseOutlineQueryKeys.all, itemId],

courseDetails: (courseId?: string) => [...courseOutlineQueryKeys.all, courseId, 'details'],
};

/**
Expand All @@ -33,3 +33,10 @@ export const useCourseItemData = (itemId?: string, enabled: boolean = true) => (
enabled: enabled && itemId !== undefined,
})
);

export const useCourseDetails = (courseId?: string) => (
useQuery({
queryKey: courseOutlineQueryKeys.courseDetails(courseId),
queryFn: courseId ? () => getCourseDetails(courseId) : skipToken,
})
);
9 changes: 9 additions & 0 deletions src/course-outline/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ export interface CourseOutline {
rerunNotificationId: null;
}

// TODO: This interface has only basic data, all the rest needs to be added.
export interface CourseDetails {
courseId: string;
title: string;
subtitle?: string;
org: string;
description?: string;
}

export interface CourseOutlineState {
loadingStatus: {
outlineIndexLoadingStatus: string;
Expand Down
1 change: 1 addition & 0 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ initialize({
ENABLE_ASSETS_PAGE: process.env.ENABLE_ASSETS_PAGE || 'false',
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN: process.env.ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN || 'false',
ENABLE_CERTIFICATE_PAGE: process.env.ENABLE_CERTIFICATE_PAGE || 'false',
ENABLE_COURSE_IMPORT_IN_LIBRARY: process.env.ENABLE_COURSE_IMPORT_IN_LIBRARY || 'false',
ENABLE_TAGGING_TAXONOMY_PAGES: process.env.ENABLE_TAGGING_TAXONOMY_PAGES || 'false',
ENABLE_CHECKLIST_QUALITY: process.env.ENABLE_CHECKLIST_QUALITY || 'true',
ENABLE_GRADING_METHOD_IN_PROBLEMS: process.env.ENABLE_GRADING_METHOD_IN_PROBLEMS === 'true',
Expand Down
4 changes: 0 additions & 4 deletions src/legacy-libraries-migration/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@

.card-item {
margin: 0 0 16px !important;

&.selected {
box-shadow: 0 0 0 2px var(--pgn-color-primary-700);
}
}
}

Expand Down
113 changes: 65 additions & 48 deletions src/library-authoring/import-course/CourseImportHomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Helmet } from 'react-helmet';
import {
Button,
Card,
Container,
Layout,
Stack,
useToggle,
} from '@openedx/paragon';
import { Add } from '@openedx/paragon/icons';
import { Helmet } from 'react-helmet';

import { getConfig } from '@edx/frontend-platform';
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
import Loading from '@src/generic/Loading';
import SubHeader from '@src/generic/sub-header/SubHeader';
Expand All @@ -17,6 +18,7 @@ import { useLibraryContext } from '../common/context/LibraryContext';
import { useCourseImports } from '../data/apiHooks';
import { HelpSidebar } from './HelpSidebar';
import { ImportedCourseCard } from './ImportedCourseCard';
import { ImportStepperModal } from './stepper/ImportStepperModal';
import messages from './messages';

const EmptyState = () => (
Expand All @@ -35,59 +37,74 @@ const EmptyState = () => (
export const CourseImportHomePage = () => {
const intl = useIntl();
const { libraryId, libraryData, readOnly } = useLibraryContext();
const [importModalIsOpen, openImportModal, closeImportModal] = useToggle(false);
const { data: courseImports } = useCourseImports(libraryId);

if (!courseImports || !libraryData) {
return <Loading />;
}

return (
<div className="d-flex">
<div className="flex-grow-1">
<Helmet>
<title>{libraryData.title} | {process.env.SITE_NAME}</title>
</Helmet>
<Header
number={libraryData.slug}
title={libraryData.title}
org={libraryData.org}
contextId={libraryId}
isLibrary
readOnly={readOnly}
containerProps={{
size: undefined,
}}
/>
<Container className="mt-4 mb-5">
<div className="px-4 bg-light-200 border-bottom">
<SubHeader
title={intl.formatMessage(messages.pageTitle)}
subtitle={intl.formatMessage(messages.pageSubtitle)}
hideBorder
/>
</div>
<Layout xs={[{ span: 9 }, { span: 3 }]}>
<Layout.Element>
{courseImports.length ? (
<Stack gap={3} className="pl-4 mt-4">
<h3>
<FormattedMessage {...messages.courseImportPreviousImports} />
</h3>
{courseImports.map((courseImport) => (
<ImportedCourseCard
key={courseImport.source.key}
courseImport={courseImport}
/>
))}
</Stack>
) : (<EmptyState />)}
</Layout.Element>
<Layout.Element>
<HelpSidebar />
</Layout.Element>
</Layout>
</Container>
<>
<div className="d-flex">
<div className="flex-grow-1">
<Helmet>
<title>{libraryData.title} | {process.env.SITE_NAME}</title>
</Helmet>
<Header
number={libraryData.slug}
title={libraryData.title}
org={libraryData.org}
contextId={libraryId}
isLibrary
readOnly={readOnly}
containerProps={{
size: undefined,
}}
/>
<Container className="mt-4 mb-5">
<div className="px-4 bg-light-200 border-bottom">
<SubHeader
title={intl.formatMessage(messages.pageTitle)}
subtitle={intl.formatMessage(messages.pageSubtitle)}
hideBorder
headerActions={
getConfig().ENABLE_COURSE_IMPORT_IN_LIBRARY === 'true' && (
<Button onClick={openImportModal}>
{intl.formatMessage(messages.importCourseButton)}
</Button>
)
}
/>
</div>
<Layout xs={[{ span: 9 }, { span: 3 }]}>
<Layout.Element>
{courseImports.length ? (
<Stack gap={3} className="pl-4 mt-4">
<h3>
<FormattedMessage {...messages.courseImportPreviousImports} />
</h3>
{courseImports.map((courseImport) => (
<ImportedCourseCard
key={courseImport.source.key}
courseImport={courseImport}
/>
))}
</Stack>
) : (<EmptyState />)}
</Layout.Element>
<Layout.Element>
<HelpSidebar />
</Layout.Element>
</Layout>
</Container>
</div>
</div>
</div>
<ImportStepperModal
isOpen={importModalIsOpen}
onClose={closeImportModal}
libraryKey={libraryId}
/>
</>
);
};
86 changes: 86 additions & 0 deletions src/library-authoring/import-course/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,92 @@ const messages = defineMessages({
+ '<p>For additional details you can review the Library Import documentation.</p>',
description: 'Body of the second question in the Help & Support sidebar',
},
importCourseModalTitle: {
id: 'course-authoring.library-authoring.import-course.modal.title',
defaultMessage: 'Import Course to Library',
description: 'Title for the modal to import a course into a library.',
},
importCourseButton: {
id: 'course-authoring.library-authoring.import-course.button.text',
defaultMessage: 'Import Course',
description: 'Label of the button to open the modal to import a course into a library.',
},
importCourseSelectCourseStep: {
id: 'course-authoring.library-authoring.import-course.select-course.title',
defaultMessage: 'Select Course',
description: 'Title for the step to select course in the modal to import a course into a library.',
},
importCourseReviewDetailsStep: {
id: 'course-authoring.library-authoring.import-course.review-details.title',
defaultMessage: 'Review Import Details',
description: 'Title for the step to review import details in the modal to import a course into a library.',
},
importCourseCalcel: {
id: 'course-authoring.library-authoring.import-course.cancel.text',
defaultMessage: 'Cancel',
description: 'Label of the button to cancel the course import.',
},
importCourseNext: {
id: 'course-authoring.library-authoring.import-course.next.text',
defaultMessage: 'Next step',
description: 'Label of the button go to the next step in the course import modal.',
},
importCourseBack: {
id: 'course-authoring.library-authoring.import-course.back.text',
defaultMessage: 'Back',
description: 'Label of the button to go to the previous step in the course import modal.',
},
importCourseInProgressStatusTitle: {
id: 'course-authoring.library-authoring.import-course.review-details.in-progress.title',
defaultMessage: 'Import Analysis in Progress',
description: 'Titile for the info card with the in-progress status in the course import modal.',
},
importCourseInProgressStatusBody: {
id: 'course-authoring.library-authoring.import-course.review-details.in-progress.body',
defaultMessage: '{courseName} is being analyzed for review prior to import. For large courses, this may take some time.'
+ ' Please remain on this page.',
description: 'Body of the info card with the in-progress status in the course import modal.',
},
importCourseAnalysisSummary: {
id: 'course-authoring.library-authoring.import-course.review-details.analysis-symmary.title',
defaultMessage: 'Analysis Summary',
description: 'Title of the card for the analysis summary of a imported course.',
},
importCourseTotalBlocks: {
id: 'course-authoring.library-authoring.import-course.review-details.analysis-symmary.total-blocks',
defaultMessage: 'Total Blocks',
description: 'Label title for the total blocks in the analysis summary of a imported course.',
},
importCourseSections: {
id: 'course-authoring.library-authoring.import-course.review-details.analysis-symmary.sections',
defaultMessage: 'Sections',
description: 'Label title for the number of sections in the analysis summary of a imported course.',
},
importCourseSubsections: {
id: 'course-authoring.library-authoring.import-course.review-details.analysis-symmary.subsections',
defaultMessage: 'Subsections',
description: 'Label title for the number of subsections in the analysis summary of a imported course.',
},
importCourseUnits: {
id: 'course-authoring.library-authoring.import-course.review-details.analysis-symmary.units',
defaultMessage: 'Units',
description: 'Label title for the number of units in the analysis summary of a imported course.',
},
importCourseComponents: {
id: 'course-authoring.library-authoring.import-course.review-details.analysis-symmary.components',
defaultMessage: 'Components',
description: 'Label title for the number of components in the analysis summary of a imported course.',
},
importCourseDetailsTitle: {
id: 'course-authoring.library-authoring.import-course.review-details.import-details.title',
defaultMessage: 'Import Details',
description: 'Title of the card for the import details of a imported course.',
},
importCourseDetailsLoadingBody: {
id: 'course-authoring.library-authoring.import-course.review-details.import-details.loading.body',
defaultMessage: 'The selected course is being analyzed for import and review',
description: 'Body of the card in loading state for the import details of a imported course.',
},
});

export default messages;
Loading