feat: add support for origin server and user info (#2663) (#2710)

* feat: add support for origin server and user info

* test: add coverage for restore archive summary

* test: increase coverage for restore archive summary

* fix: address comments
This commit is contained in:
Daniel Wong
2025-12-04 13:24:06 -06:00
committed by GitHub
parent 28f0c9943d
commit 2ba6f96142
3 changed files with 91 additions and 9 deletions

View File

@@ -435,7 +435,7 @@ describe('<CreateLibrary />', () => {
sections: 8,
subsections: 12,
units: 20,
createdOnServer: '2025-01-01T10:00:00Z',
createdOnServer: 'test.com',
createdAt: '2025-01-01T10:00:00Z',
createdBy: {
username: 'testuser',
@@ -478,7 +478,67 @@ describe('<CreateLibrary />', () => {
await waitFor(() => {
expect(screen.getByText('Test Archive Library')).toBeInTheDocument();
expect(screen.getByText('TestOrg / test-archive')).toBeInTheDocument();
expect(screen.getByText(/Contains 15 Components/i)).toBeInTheDocument();
// Testing the archive details summary
expect(screen.getByText(/Contains 8 sections, 12 subsections, 20 units, 15 components/i)).toBeInTheDocument();
expect(screen.getByText(/Created on instance test.com/i)).toBeInTheDocument();
expect(screen.getByText(/by user test@example.com/i)).toBeInTheDocument();
});
});
test('shows success state without instance and user email information', async () => {
const user = userEvent.setup();
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
const mockResult = {
learningPackageId: 123,
title: 'Test Archive Library',
org: 'TestOrg',
slug: 'test-archive',
key: 'TestOrg/test-archive',
archiveKey: 'archive-key',
containers: 5,
components: 15,
collections: 3,
sections: 8,
subsections: 12,
units: 20,
createdOnServer: null,
createdAt: '2025-01-01T10:00:00Z',
createdBy: null,
};
// Pre-set the restore status to succeeded
mockRestoreStatusData = {
state: LibraryRestoreStatus.Succeeded,
result: mockResult,
error: null,
errorLog: null,
};
// Mock the restore mutation to return a task ID
mockRestoreMutate.mockImplementation((_file: File, { onSuccess }: any) => {
onSuccess({ taskId: 'task-123' });
});
render(<CreateLibrary />);
// Switch to archive mode
const createFromArchiveBtn = await screen.findByRole('button', { name: messages.createFromArchiveButton.defaultMessage });
await user.click(createFromArchiveBtn);
// Upload a file to trigger the restore process
const file = new File(['test content'], 'test-archive.zip', { type: 'application/zip' });
const dropzone = screen.getByRole('presentation', { hidden: true });
const input = dropzone.querySelector('input[type="file"]') as HTMLInputElement;
await user.upload(input, file);
// Wait for the restore to complete and archive details to be shown
await waitFor(() => {
// Testing the archive details summary without instance and user email
expect(screen.getByText(/Contains 8 sections, 12 subsections, 20 units, 15 components/i)).toBeInTheDocument();
expect(screen.queryByText(/Created on instance/i)).not.toBeInTheDocument();
expect(screen.queryByText(/by user/i)).not.toBeInTheDocument();
});
});

View File

@@ -15,6 +15,7 @@ import {
import {
AccessTime,
Widgets,
PersonOutline,
} from '@openedx/paragon/icons';
import AlertError from '@src/generic/alert-error';
import classNames from 'classnames';
@@ -203,22 +204,38 @@ export const CreateLibrary = ({
<Card.Body>
<div className="d-flex flex-column flex-md-row justify-content-between align-items-start p-4 text-primary-700">
<div className="flex-grow-1 mb-4 mb-md-0">
<span className="mb-2">{restoreStatus.result.title}</span>
<span className="mb-4">{restoreStatus.result.title}</span>
<p className="small mb-0">
{restoreStatus.result.org} / {restoreStatus.result.slug}
</p>
</div>
<div className="d-flex flex-column gap-2 align-items-md-end">
<div className="d-flex flex-column gap-2 align-items-md-start">
<div className="d-flex align-items-md-center gap-2">
<Icon src={Widgets} style={{ width: '20px', height: '20px', marginRight: '8px' }} />
<Icon src={Widgets} className="mr-2" style={{ width: '20px', height: '20px' }} />
<span className="x-small">
{intl.formatMessage(messages.archiveComponentsCount, {
count: restoreStatus.result.components,
countSections: restoreStatus.result.sections,
countSubsections: restoreStatus.result.subsections,
countUnits: restoreStatus.result.units,
countComponents: restoreStatus.result.components,
})}
</span>
</div>
{
(restoreStatus.result.createdBy?.email && restoreStatus.result.createdOnServer) && (
<div className="d-flex align-items-md-center gap-2">
<Icon src={PersonOutline} className="mr-2" style={{ width: '20px', height: '20px' }} />
<span className="x-small">
{intl.formatMessage(messages.archiveRestoredCreatedBy, {
createdBy: restoreStatus.result.createdBy?.email,
server: restoreStatus.result.createdOnServer,
})}
</span>
</div>
)
}
<div className="d-flex align-items-md-center gap-2">
<Icon src={AccessTime} style={{ width: '20px', height: '20px', marginRight: '8px' }} />
<Icon src={AccessTime} className="mr-2" style={{ width: '20px', height: '20px' }} />
<span className="x-small">
{intl.formatMessage(messages.archiveBackupDate, {
date: new Date(restoreStatus.result.createdAt).toLocaleDateString(),

View File

@@ -120,8 +120,13 @@ const messages = defineMessages({
},
archiveComponentsCount: {
id: 'course-authoring.library-authoring.create-library.form.archive.components-count',
defaultMessage: 'Contains {count} Components',
description: 'Text showing the number of components in the restored archive.',
defaultMessage: 'Contains {countSections} sections, {countSubsections} subsections, {countUnits} units, {countComponents} components',
description: 'Text showing the number of sections, subsections, units, and components in the restored archive.',
},
archiveRestoredCreatedBy: {
id: 'course-authoring.library-authoring.create-library.form.archive.restored-created-by',
defaultMessage: 'Created on instance {server}, by user {createdBy}',
description: 'Text showing who restored the archive.',
},
archiveBackupDate: {
id: 'course-authoring.library-authoring.create-library.form.archive.backup-date',