feat: Legacy libraries migration help sidebar [FC-0097] (#2503)

- Implements the Legacy Libraries Migration Help Sidebar
- Shows the sidebar in the studio home
- Shows the sidebar in the Legacy Libraries Migration Page
This commit is contained in:
Chris Chávez
2025-10-15 15:35:08 -05:00
committed by GitHub
parent 4a26a86c90
commit 195249ef26
5 changed files with 223 additions and 74 deletions

View File

@@ -53,9 +53,9 @@ describe('<LegacyLibMigrationPage />', () => {
// Should render the title
expect(await screen.findByText('Migrate Legacy Libraries')).toBeInTheDocument();
// Should render the Migration Steps Viewer
expect(screen.getByText(/select legacy libraries/i)).toBeInTheDocument();
expect(screen.getByText(/select destination/i)).toBeInTheDocument();
expect(screen.getByText(/confirm/i)).toBeInTheDocument();
expect(screen.getByText('Select Legacy Libraries')).toBeInTheDocument();
expect(screen.getByText('Select Destination')).toBeInTheDocument();
expect(screen.getByText('Confirm')).toBeInTheDocument();
});
it('should cancel the migration', async () => {
@@ -401,4 +401,12 @@ describe('<LegacyLibMigrationPage />', () => {
);
expect(mockShowToast).toHaveBeenCalledWith('Legacy libraries migration failed.');
});
it('should show help sidebar', async () => {
renderPage();
expect(await screen.findByText('Help & Support')).toBeInTheDocument();
expect(screen.getByText('Whats different in the new Content Libraries experience?')).toBeInTheDocument();
expect(screen.getByText('What happens when I migrate my Legacy Libraries?')).toBeInTheDocument();
expect(screen.getByText('How do I migrate my Legacy Libraries?')).toBeInTheDocument();
});
});

View File

@@ -12,6 +12,7 @@ import {
ActionRow,
Button,
Container,
Layout,
ModalDialog,
StatefulButton,
Stepper,
@@ -27,6 +28,7 @@ import { Filter, LibrariesList } from '@src/studio-home/tabs-section/libraries-t
import messages from './messages';
import { SelectDestinationView } from './SelectDestinationView';
import { ConfirmationView } from './ConfirmationView';
import { LegacyMigrationHelpSidebar } from './LegacyMigrationHelpSidebar';
import { useUpdateContainerCollections } from './data/apiHooks';
export type MigrationStep = 'select-libraries' | 'select-destination' | 'confirmation-view';
@@ -164,79 +166,90 @@ export const LegacyLibMigrationPage = () => {
return (
<>
<Helmet>
<title>
{intl.formatMessage(messages.siteTitle)}
</title>
</Helmet>
<Header isHiddenMainMenu />
<div className="legacy-library-migration-page">
<Helmet>
<title>
{intl.formatMessage(messages.siteTitle)}
</title>
</Helmet>
<Header isHiddenMainMenu />
<Container className="migration-container d-flex flex-column px-6 mt-5 mb-5">
<div className="migration-content">
<SubHeader
title={intl.formatMessage(messages.siteTitle)}
/>
<Stepper activeKey={currentStep}>
<Stepper.Header />
<Stepper.Step
eventKey="select-libraries"
title={intl.formatMessage(messages.selectLegacyLibrariesStepTitle)}
>
<LibrariesList
selectedIds={legacyLibrariesIds}
handleCheck={handleUpdateLegacyLibraries}
hideMigationAlert
initialFilter={[Filter.unmigrated]}
setSelectedLibraries={setLegacyLibraries}
/>
</Stepper.Step>
<Stepper.Step
eventKey="select-destination"
title={intl.formatMessage(messages.selectDestinationStepTitle)}
>
<SelectDestinationView
destinationId={destinationLibrary?.id}
setDestinationId={setDestination}
legacyLibCount={legacyLibraries.length}
/>
</Stepper.Step>
<Stepper.Step
eventKey="confirmation-view"
title={intl.formatMessage(messages.confirmStepTitle)}
>
{destinationLibrary && (
<ConfirmationView
destination={destinationLibrary}
legacyLibraries={legacyLibraries}
<Layout
xs={[{ span: 9 }, { span: 3 }]}
>
<Layout.Element>
<div className="flex-fill">
<Container className="migration-container d-flex flex-column px-6 mt-5 mb-5">
<div className="migration-content">
<SubHeader
title={intl.formatMessage(messages.siteTitle)}
/>
<Stepper activeKey={currentStep}>
<Stepper.Header />
<Stepper.Step
eventKey="select-libraries"
title={intl.formatMessage(messages.selectLegacyLibrariesStepTitle)}
>
<LibrariesList
selectedIds={legacyLibrariesIds}
handleCheck={handleUpdateLegacyLibraries}
hideMigationAlert
initialFilter={[Filter.unmigrated]}
setSelectedLibraries={setLegacyLibraries}
/>
</Stepper.Step>
<Stepper.Step
eventKey="select-destination"
title={intl.formatMessage(messages.selectDestinationStepTitle)}
>
<SelectDestinationView
destinationId={destinationLibrary?.id}
setDestinationId={setDestination}
legacyLibCount={legacyLibraries.length}
/>
</Stepper.Step>
<Stepper.Step
eventKey="confirmation-view"
title={intl.formatMessage(messages.confirmStepTitle)}
>
{destinationLibrary && (
<ConfirmationView
destination={destinationLibrary}
legacyLibraries={legacyLibraries}
/>
)}
</Stepper.Step>
</Stepper>
</div>
</Container>
<div className="content-buttons d-flex justify-content-between pl-6 pr-6 bg-white">
<Button className="mt-2 mb-2" variant="outline-primary" onClick={handleBack}>
{currentStep === 'select-libraries'
? intl.formatMessage(messages.cancel)
: intl.formatMessage(messages.back)}
</Button>
{currentStep !== 'confirmation-view' ? (
<Button className="mt-2 mb-2" onClick={handleNext} disabled={isNextDisabled()}>
{intl.formatMessage(messages.next)}
</Button>
) : (
<StatefulButton
className="mt-2 mb-2"
state={confirmationButtonState}
disabledStates={['pending']}
labels={{
default: intl.formatMessage(messages.confirm),
pending: intl.formatMessage(messages.confirm),
}}
onClick={handleNext}
/>
)}
</Stepper.Step>
</Stepper>
</div>
</Container>
<div className="content-buttons d-flex justify-content-between pl-6 pr-6 bg-white">
<Button className="mt-2 mb-2" variant="outline-primary" onClick={handleBack}>
{currentStep === 'select-libraries'
? intl.formatMessage(messages.cancel)
: intl.formatMessage(messages.back)}
</Button>
{currentStep !== 'confirmation-view' ? (
<Button className="mt-2 mb-2" onClick={handleNext} disabled={isNextDisabled()}>
{intl.formatMessage(messages.next)}
</Button>
) : (
<StatefulButton
className="mt-2 mb-2"
state={confirmationButtonState}
disabledStates={['pending']}
labels={{
default: intl.formatMessage(messages.confirm),
pending: intl.formatMessage(messages.confirm),
}}
onClick={handleNext}
/>
)}
</div>
</div>
</div>
</Layout.Element>
<Layout.Element>
<LegacyMigrationHelpSidebar />
</Layout.Element>
</Layout>
</div>
<ExitModal
isExitModalOpen={isExitModalOpen}

View File

@@ -0,0 +1,51 @@
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Icon, Stack } from '@openedx/paragon';
import { Question } from '@openedx/paragon/icons';
import messages from './messages';
export const SingleLineBreak = (chunk: string[]) => <div>{chunk}</div>;
export const Paragraph = (chunk: string[]) => <p>{chunk}</p>;
export const LegacyMigrationHelpSidebar = () => (
<div className="legacy-libraries-migration-help bg-white pt-3 mt-1">
<Stack gap={1} direction="horizontal" className="pl-4 h4 text-primary-700">
<Icon src={Question} />
<span>
<FormattedMessage {...messages.helpAndSupportTitle} />
</span>
</Stack>
<hr />
<Stack className="pl-4 pr-4">
<Stack>
<span className="h5">
<FormattedMessage {...messages.helpAndSupportFirstQuestionTitle} />
</span>
<span className="x-small">
<FormattedMessage {...messages.helpAndSupportFirstQuestionBody} />
</span>
</Stack>
<hr />
<Stack>
<span className="h5">
<FormattedMessage {...messages.helpAndSupportSecondQuestionTitle} />
</span>
<span className="x-small">
<FormattedMessage {...messages.helpAndSupportSecondQuestionBody} />
</span>
</Stack>
<hr />
<Stack>
<span className="h5">
<FormattedMessage {...messages.helpAndSupportThirdQuestionTitle} />
</span>
<span className="x-small">
<FormattedMessage
{...messages.helpAndSupportThirdQuestionBody}
values={{ div: SingleLineBreak, p: Paragraph }}
/>
</span>
</Stack>
</Stack>
</div>
);

View File

@@ -1,5 +1,8 @@
.legacy-library-migration-page {
.migration-container {
// Calculate all the screen size subtracting the height of the header and top/bottom margins
min-height: calc(calc(100vh - 60px) - calc(var(--pgn-spacing-spacer-base) * 6));
.courses-tab-container {
min-height: auto;
}
@@ -25,7 +28,29 @@
.content-buttons {
width: 100%;
position: fixed;
position: sticky;
bottom: 0;
}
.row {
margin-right: 0; // To avoid create a horizontal scroll using Layout
}
[class*="col-"] {
// To avoid create an empty gray space between the main content and the sidebar
padding-right: 0;
padding-left: 0;
}
}
.legacy-libraries-migration-help {
z-index: 1000; // same as header
flex: 350px 0 0;
position: sticky;
top: 0;
right: 0;
hr {
width: 100%;
}
}

View File

@@ -82,6 +82,58 @@ const messages = defineMessages({
+ ' moved will be migrated to <b>{libraryName}</b>',
description: 'Alert text when the legacy library is already migrated.',
},
helpAndSupportTitle: {
id: 'legacy-libraries-migration.helpAndSupport.title',
defaultMessage: 'Help & Support',
description: 'Title of the Help & Support sidebar',
},
helpAndSupportFirstQuestionTitle: {
id: 'legacy-libraries-migration.helpAndSupport.q1.title',
defaultMessage: 'Whats different in the new Content Libraries experience?',
description: 'Title of the first question in the Help & Support sidebar',
},
helpAndSupportFirstQuestionBody: {
id: 'legacy-libraries-migration.helpAndSupport.q1.body',
defaultMessage: 'In the new Content Libraries experience, you can author sections,'
+ ' subsections, units, and many types of components. Library content can be reused across many courses,'
+ ' and kept up-to-date. Content libraries now support increased collaboration across authoring teams.',
description: 'Body of the first question in the Help & Support sidebar',
},
helpAndSupportSecondQuestionTitle: {
id: 'legacy-libraries-migration.helpAndSupport.q2.title',
defaultMessage: 'What happens when I migrate my Legacy Libraries?',
description: 'Title of the second question in the Help & Support sidebar',
},
helpAndSupportSecondQuestionBody: {
id: 'legacy-libraries-migration.helpAndSupport.q2.body',
defaultMessage: 'All legacy library content is supported in the new experience.'
+ ' Content from legacy libraries will be migrated to its own collection in the new Content Libraries experience.'
+ ' This collection will have the same name as your original library. Courses that use legacy library content will'
+ ' continue to function as usual, linked to the migrated version within the new libraries experience.',
description: 'Body of the second question in the Help & Support sidebar',
},
helpAndSupportThirdQuestionTitle: {
id: 'legacy-libraries-migration.helpAndSupport.q3.title',
defaultMessage: 'How do I migrate my Legacy Libraries?',
description: 'Title of the third question in the Help & Support sidebar',
},
helpAndSupportThirdQuestionBody: {
id: 'legacy-libraries-migration.helpAndSupport.q3.body.2',
defaultMessage: '<p>There are three steps to migrating legacy libraries:</p>'
+ '<p><div>1 - Select Legacy Libraries</div>'
+ 'You can select up to 50 legacy libraries for migration in this step. By default, only libraries that have'
+ ' not yet been migrated are shown. To see all libraries, remove the filter.'
+ ' You can select up to 50 legacy libraries for migration, but only one destination'
+ ' v2 Content Library per migration.</p>'
+ '<p><div>2 - Select Destination</div>'
+ 'You can migrate legacy libraries to an existing Content Library in the new experience,'
+ ' or you can create a new destination. You can only select one v2 Content Library per migration.'
+ ' All your content will be migrated, and kept organized in collections.</p>'
+ '<p><div>3 - Confirm</div>'
+ 'In this step, review your migration. Once you confirm, migration will begin.'
+ ' It may take some time to complete.</p>',
description: 'Part 2 of the Body of the third question in the Help & Support sidebar',
},
migrationInProgress: {
id: 'legacy-libraries-migration.confirmation-step.toast.migration-in-progress',
defaultMessage: '{count, plural, one {{count} legacy library is} other {{count} legacy libraries are}} being migrated.',