Compare commits
1 Commits
release/ul
...
chris/FAL-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49c28de286 |
@@ -35,6 +35,7 @@ import { ContentType } from './library-authoring/routes';
|
|||||||
|
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
import { LegacyLibMigrationPage } from './legacy-libraries-migration/LegacyLibMigrationPage';
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
@@ -65,6 +66,7 @@ const App = () => {
|
|||||||
<Route path="/home" element={<StudioHome />} />
|
<Route path="/home" element={<StudioHome />} />
|
||||||
<Route path="/libraries" element={<StudioHome />} />
|
<Route path="/libraries" element={<StudioHome />} />
|
||||||
<Route path="/libraries-v1" element={<StudioHome />} />
|
<Route path="/libraries-v1" element={<StudioHome />} />
|
||||||
|
<Route path="/libraries-v1/migrate" element={<LegacyLibMigrationPage />} />
|
||||||
<Route path="/library/create" element={<CreateLibrary />} />
|
<Route path="/library/create" element={<CreateLibrary />} />
|
||||||
<Route path="/library/:libraryId/*" element={<LibraryLayout />} />
|
<Route path="/library/:libraryId/*" element={<LibraryLayout />} />
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
7
src/legacy-libraries-migration/ConfirmationView.tsx
Normal file
7
src/legacy-libraries-migration/ConfirmationView.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Container } from '@openedx/paragon';
|
||||||
|
|
||||||
|
export const ConfirmatiobView = () => (
|
||||||
|
<Container>
|
||||||
|
Confirmation View
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import {
|
||||||
|
initializeMocks,
|
||||||
|
render,
|
||||||
|
screen,
|
||||||
|
waitFor,
|
||||||
|
} from '@src/testUtils';
|
||||||
|
|
||||||
|
import { LegacyLibMigrationPage } from './LegacyLibMigrationPage';
|
||||||
|
|
||||||
|
const path = '/libraries-v1/migrate/*';
|
||||||
|
|
||||||
|
const mockNavigate = jest.fn();
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...jest.requireActual('react-router-dom'),
|
||||||
|
useNavigate: () => mockNavigate,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const renderPage = () => (
|
||||||
|
render(<LegacyLibMigrationPage />, { path })
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('<LegacyLibMigrationPage />', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
initializeMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render legacy library migration page', async () => {
|
||||||
|
renderPage();
|
||||||
|
// 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cancel the migration', async () => {
|
||||||
|
renderPage();
|
||||||
|
expect(await screen.findByText('Migrate Legacy Libraries')).toBeInTheDocument();
|
||||||
|
|
||||||
|
const cancelButton = screen.getByRole('button', { name: /cancel/i });
|
||||||
|
cancelButton.click();
|
||||||
|
|
||||||
|
// Should show exit confirmation modal
|
||||||
|
expect(await screen.findByText('Exit Migration?')).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Close exit confirmation modal
|
||||||
|
const continueButton = screen.getByRole('button', { name: /continue migrating/i });
|
||||||
|
continueButton.click();
|
||||||
|
expect(mockNavigate).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
cancelButton.click();
|
||||||
|
|
||||||
|
// Should navigate to legacy libraries tab on studio home
|
||||||
|
expect(await screen.findByText('Exit Migration?')).toBeInTheDocument();
|
||||||
|
const exitButton = screen.getByRole('button', { name: /exit/i });
|
||||||
|
exitButton.click();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockNavigate).toHaveBeenCalledWith('/libraries-v1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
153
src/legacy-libraries-migration/LegacyLibMigrationPage.tsx
Normal file
153
src/legacy-libraries-migration/LegacyLibMigrationPage.tsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import {
|
||||||
|
ActionRow,
|
||||||
|
Button,
|
||||||
|
Container,
|
||||||
|
ModalDialog,
|
||||||
|
Stepper,
|
||||||
|
useToggle,
|
||||||
|
} from '@openedx/paragon';
|
||||||
|
import Header from '@src/header';
|
||||||
|
import SubHeader from '@src/generic/sub-header/SubHeader';
|
||||||
|
|
||||||
|
import messages from './messages';
|
||||||
|
import { SelectLegacyLibraryView } from './SelectLegacyLibraryView';
|
||||||
|
import { SelectDestinationView } from './SelectDestinationView';
|
||||||
|
import { ConfirmatiobView } from './ConfirmationView';
|
||||||
|
import { MigrationStepsViewer } from './MigrationStepsViewer';
|
||||||
|
|
||||||
|
export type MigrationStep = 'select-libraries' | 'select-destination' | 'confirmation-view';
|
||||||
|
|
||||||
|
const ExitModal = ({
|
||||||
|
isExitModalOpen,
|
||||||
|
closeExitModal,
|
||||||
|
}: {
|
||||||
|
isExitModalOpen: boolean,
|
||||||
|
closeExitModal: () => void,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleExit = useCallback(() => {
|
||||||
|
navigate('/libraries-v1');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalDialog
|
||||||
|
title={intl.formatMessage(messages.exitModalTitle)}
|
||||||
|
isOpen={isExitModalOpen}
|
||||||
|
onClose={closeExitModal}
|
||||||
|
isOverflowVisible={false}
|
||||||
|
hasCloseButton={false}
|
||||||
|
>
|
||||||
|
<ModalDialog.Header>
|
||||||
|
<ModalDialog.Title>
|
||||||
|
{intl.formatMessage(messages.exitModalTitle)}
|
||||||
|
</ModalDialog.Title>
|
||||||
|
</ModalDialog.Header>
|
||||||
|
<ModalDialog.Body>
|
||||||
|
{intl.formatMessage(messages.exitModalBodyText)}
|
||||||
|
</ModalDialog.Body>
|
||||||
|
<ModalDialog.Footer>
|
||||||
|
<ActionRow>
|
||||||
|
<ModalDialog.CloseButton variant="tertiary">
|
||||||
|
{intl.formatMessage(messages.exitModalCancelText)}
|
||||||
|
</ModalDialog.CloseButton>
|
||||||
|
<Button onClick={handleExit}>
|
||||||
|
{intl.formatMessage(messages.exitModalConfirmText)}
|
||||||
|
</Button>
|
||||||
|
</ActionRow>
|
||||||
|
</ModalDialog.Footer>
|
||||||
|
</ModalDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LegacyLibMigrationPage = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const [currentStep, setCurrentStep] = useState<MigrationStep>('select-libraries');
|
||||||
|
const [isExitModalOpen, openExitModal, closeExitModal] = useToggle(false);
|
||||||
|
|
||||||
|
const handleNext = useCallback(() => {
|
||||||
|
switch (currentStep) {
|
||||||
|
case 'select-libraries':
|
||||||
|
setCurrentStep('select-destination');
|
||||||
|
break;
|
||||||
|
case 'select-destination':
|
||||||
|
setCurrentStep('confirmation-view');
|
||||||
|
break;
|
||||||
|
case 'confirmation-view':
|
||||||
|
// Handle confirm
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [currentStep, setCurrentStep]);
|
||||||
|
|
||||||
|
const handleBack = useCallback(() => {
|
||||||
|
switch (currentStep) {
|
||||||
|
case 'select-libraries':
|
||||||
|
openExitModal();
|
||||||
|
break;
|
||||||
|
case 'select-destination':
|
||||||
|
setCurrentStep('select-libraries');
|
||||||
|
break;
|
||||||
|
case 'confirmation-view':
|
||||||
|
setCurrentStep('select-destination');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [currentStep, setCurrentStep]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="d-flex">
|
||||||
|
<div className="flex-grow-1">
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
{intl.formatMessage(messages.siteTitle)}
|
||||||
|
</title>
|
||||||
|
</Helmet>
|
||||||
|
<Header isHiddenMainMenu />
|
||||||
|
<Container className="px-6 mt-5 mb-5">
|
||||||
|
<SubHeader
|
||||||
|
title={intl.formatMessage(messages.siteTitle)}
|
||||||
|
/>
|
||||||
|
<MigrationStepsViewer currentStep={currentStep} />
|
||||||
|
<Stepper activeKey={currentStep}>
|
||||||
|
<Stepper.Step eventKey="select-libraries" title="Select Legacy Libraries">
|
||||||
|
<SelectLegacyLibraryView />
|
||||||
|
</Stepper.Step>
|
||||||
|
<Stepper.Step eventKey="select-destination" title="Select Destination">
|
||||||
|
<SelectDestinationView />
|
||||||
|
</Stepper.Step>
|
||||||
|
<Stepper.Step eventKey="confirmation-view" title="Confirmation">
|
||||||
|
<ConfirmatiobView />
|
||||||
|
</Stepper.Step>
|
||||||
|
</Stepper>
|
||||||
|
<div className="d-flex justify-content-between">
|
||||||
|
<Button variant="outline-primary" onClick={handleBack}>
|
||||||
|
{currentStep === 'select-libraries'
|
||||||
|
? intl.formatMessage(messages.cancel)
|
||||||
|
: intl.formatMessage(messages.back)}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleNext}>
|
||||||
|
{currentStep === 'confirmation-view'
|
||||||
|
? intl.formatMessage(messages.confirm)
|
||||||
|
: intl.formatMessage(messages.next)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ExitModal
|
||||||
|
isExitModalOpen={isExitModalOpen}
|
||||||
|
closeExitModal={closeExitModal}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
80
src/legacy-libraries-migration/MigrationStepsViewer.tsx
Normal file
80
src/legacy-libraries-migration/MigrationStepsViewer.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import type { MessageDescriptor } from 'react-intl';
|
||||||
|
import {
|
||||||
|
Bubble,
|
||||||
|
Container,
|
||||||
|
Icon,
|
||||||
|
Stack,
|
||||||
|
} from '@openedx/paragon';
|
||||||
|
import { Check } from '@openedx/paragon/icons';
|
||||||
|
|
||||||
|
import type { MigrationStep } from './LegacyLibMigrationPage';
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
export const MigrationStepsViewer = ({ currentStep }: { currentStep: MigrationStep }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const stepNumbers: Record<MigrationStep, number> = {
|
||||||
|
'select-libraries': 1,
|
||||||
|
'select-destination': 2,
|
||||||
|
'confirmation-view': 3,
|
||||||
|
};
|
||||||
|
const stepNames: Record<MigrationStep, MessageDescriptor> = {
|
||||||
|
'select-libraries': messages.selectLegacyLibrariesStepTitle,
|
||||||
|
'select-destination': messages.selectDestinationStepTitle,
|
||||||
|
'confirmation-view': messages.confirmStepTitle,
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkStep = (step: MigrationStep) => {
|
||||||
|
if (currentStep === step) {
|
||||||
|
return 'current';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (step) {
|
||||||
|
case 'select-libraries':
|
||||||
|
// If is not current, then is done.
|
||||||
|
return 'done';
|
||||||
|
case 'select-destination':
|
||||||
|
if (currentStep === 'select-libraries') {
|
||||||
|
return 'disabled';
|
||||||
|
}
|
||||||
|
return 'done';
|
||||||
|
case 'confirmation-view':
|
||||||
|
// If is not current, then is disabled.
|
||||||
|
return 'disabled';
|
||||||
|
default:
|
||||||
|
return 'disabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'disabled';
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildStep = (step: MigrationStep) => {
|
||||||
|
const stepStatus = checkStep(step);
|
||||||
|
return (
|
||||||
|
<Stack direction="horizontal">
|
||||||
|
<Bubble className="mr-2" disabled={stepStatus === 'disabled'}>
|
||||||
|
{stepStatus === 'done' ? (
|
||||||
|
<Icon src={Check} />
|
||||||
|
) : (
|
||||||
|
stepNumbers[step]
|
||||||
|
)}
|
||||||
|
</Bubble>
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(stepNames[step])}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="d-flex justify-content-center migration-steps-viewer">
|
||||||
|
{buildStep('select-libraries')}
|
||||||
|
<hr className="ml-3 mr-3" style={{ width: '80px' }} />
|
||||||
|
{buildStep('select-destination')}
|
||||||
|
<hr className="ml-3 mr-3" style={{ width: '80px' }} />
|
||||||
|
{buildStep('confirmation-view')}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
7
src/legacy-libraries-migration/SelectDestinationView.tsx
Normal file
7
src/legacy-libraries-migration/SelectDestinationView.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Container } from '@openedx/paragon';
|
||||||
|
|
||||||
|
export const SelectDestinationView = () => (
|
||||||
|
<Container>
|
||||||
|
SelectDestinationView
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Container } from '@openedx/paragon';
|
||||||
|
|
||||||
|
export const SelectLegacyLibraryView = () => (
|
||||||
|
<Container>
|
||||||
|
Select Legacy LibraryStep
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
66
src/legacy-libraries-migration/messages.ts
Normal file
66
src/legacy-libraries-migration/messages.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
siteTitle: {
|
||||||
|
id: 'legacy-libraries-migration.site-title',
|
||||||
|
defaultMessage: 'Migrate Legacy Libraries',
|
||||||
|
description: 'Title for the page to migrate legacy libraries.',
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
id: 'legacy-libraries-migration.button.cancel',
|
||||||
|
defaultMessage: 'Cancel',
|
||||||
|
description: 'Text of the button to cancel the migration.',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
id: 'legacy-libraries-migration.button.next',
|
||||||
|
defaultMessage: 'Next',
|
||||||
|
description: 'Text of the button to go to the next step of the migration.',
|
||||||
|
},
|
||||||
|
back: {
|
||||||
|
id: 'legacy-libraries-migration.button.back',
|
||||||
|
defaultMessage: 'Back',
|
||||||
|
description: 'Text of the button to go back to the previous step of the migration.',
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
id: 'legacy-libraries-migration.button.confirm',
|
||||||
|
defaultMessage: 'Confirm',
|
||||||
|
description: 'Text of the button to confirm the migration.',
|
||||||
|
},
|
||||||
|
selectLegacyLibrariesStepTitle: {
|
||||||
|
id: 'legacy-libraries-migration.select-legacy-libraries-step.title',
|
||||||
|
defaultMessage: 'Select Legacy Libraries',
|
||||||
|
description: 'Title of the Select Legacy Libraries step',
|
||||||
|
},
|
||||||
|
selectDestinationStepTitle: {
|
||||||
|
id: 'legacy-libraries-migration.select-destination-step.title',
|
||||||
|
defaultMessage: 'Select Destination',
|
||||||
|
description: 'Title of the Select Destination step',
|
||||||
|
},
|
||||||
|
confirmStepTitle: {
|
||||||
|
id: 'legacy-libraries-migration.confirm-step.title',
|
||||||
|
defaultMessage: 'Confirm',
|
||||||
|
description: 'Title of the Confirm step',
|
||||||
|
},
|
||||||
|
exitModalTitle: {
|
||||||
|
id: 'legacy-libraries-migration.exit-modal.title',
|
||||||
|
defaultMessage: 'Exit Migration?',
|
||||||
|
description: 'Title of the modal to confirm exit the migration.',
|
||||||
|
},
|
||||||
|
exitModalBodyText: {
|
||||||
|
id: 'legacy-libraries-migration.exit-modal.body',
|
||||||
|
defaultMessage: 'By exiting, all changes will be lost and no libraries will be migrated.',
|
||||||
|
description: 'Body text of the modal to confirm exit the migration.',
|
||||||
|
},
|
||||||
|
exitModalCancelText: {
|
||||||
|
id: 'legacy-libraries-migration.exit-modal.button.cancel.text',
|
||||||
|
defaultMessage: 'Continue Migrating',
|
||||||
|
description: 'Text for the button to close the modal to confirm exit the migration.',
|
||||||
|
},
|
||||||
|
exitModalConfirmText: {
|
||||||
|
id: 'legacy-libraries-migration.exit-modal.button.confirm.text',
|
||||||
|
defaultMessage: 'Exit',
|
||||||
|
description: 'Text for the button to confirm exit the migration.',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messages;
|
||||||
Reference in New Issue
Block a user