feat: include usage locations in delete modal (#938)
This commit is contained in:
@@ -331,7 +331,7 @@ describe('FilesAndUploads', () => {
|
||||
axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID1`).reply(204);
|
||||
|
||||
fireEvent.click(deleteButton);
|
||||
expect(screen.getByText('Delete file(s) confirmation')).toBeVisible();
|
||||
expect(screen.getByText('Delete mOckID1')).toBeVisible();
|
||||
await act(async () => {
|
||||
userEvent.click(deleteButton);
|
||||
});
|
||||
@@ -345,7 +345,7 @@ describe('FilesAndUploads', () => {
|
||||
userEvent.click(confirmDeleteButton);
|
||||
});
|
||||
|
||||
expect(screen.queryByText('Delete file(s) confirmation')).toBeNull();
|
||||
expect(screen.queryByText('Delete mOckID1')).toBeNull();
|
||||
|
||||
// Check if the asset is deleted in the store and UI
|
||||
const deleteStatus = store.getState().assets.deletingStatus;
|
||||
@@ -554,10 +554,10 @@ describe('FilesAndUploads', () => {
|
||||
axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID1`).reply(204);
|
||||
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
|
||||
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
|
||||
expect(screen.getByText('Delete file(s) confirmation')).toBeVisible();
|
||||
expect(screen.getByText('Delete mOckID1')).toBeVisible();
|
||||
|
||||
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
|
||||
expect(screen.queryByText('Delete file(s) confirmation')).toBeNull();
|
||||
expect(screen.queryByText('Delete mOckID1')).toBeNull();
|
||||
|
||||
executeThunk(deleteAssetFile(courseId, 'mOckID1', 5), store.dispatch);
|
||||
});
|
||||
@@ -644,10 +644,10 @@ describe('FilesAndUploads', () => {
|
||||
axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID3`).reply(404);
|
||||
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
|
||||
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
|
||||
expect(screen.getByText('Delete file(s) confirmation')).toBeVisible();
|
||||
expect(screen.getByText('Delete mOckID3')).toBeVisible();
|
||||
|
||||
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
|
||||
expect(screen.queryByText('Delete file(s) confirmation')).toBeNull();
|
||||
expect(screen.queryByText('Delete mOckID3')).toBeNull();
|
||||
|
||||
executeThunk(deleteAssetFile(courseId, 'mOckID3', 5), store.dispatch);
|
||||
});
|
||||
|
||||
@@ -4,82 +4,92 @@ const messages = defineMessages({
|
||||
heading: {
|
||||
id: 'course-authoring.files-and-uploads.heading',
|
||||
defaultMessage: 'Files',
|
||||
description: 'Title for the page',
|
||||
},
|
||||
thumbnailAltMessage: {
|
||||
id: 'course-authoring.files-and-uploads.thumbnail.alt',
|
||||
defaultMessage: '{displayName} file preview',
|
||||
description: 'Alternative text for thumbnail',
|
||||
},
|
||||
copyStudioUrlTitle: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.copyStudioUrl.title',
|
||||
defaultMessage: 'Copy Studio Url',
|
||||
description: 'Label for Copy Studio URL button in info modal',
|
||||
},
|
||||
copyWebUrlTitle: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.copyWebUrl.title',
|
||||
defaultMessage: 'Copy Web Url',
|
||||
description: 'Label for Copy Web URL button in info modal',
|
||||
},
|
||||
dateAddedTitle: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.dateAdded.title',
|
||||
defaultMessage: 'Date added',
|
||||
description: 'Title for date added section in info modal',
|
||||
},
|
||||
fileSizeTitle: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.fileSize.title',
|
||||
defaultMessage: 'File size',
|
||||
description: 'Title for file size section in info modal',
|
||||
},
|
||||
studioUrlTitle: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.studioUrl.title',
|
||||
defaultMessage: 'Studio URL',
|
||||
description: 'Title for studio url section in info modal',
|
||||
},
|
||||
webUrlTitle: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.webUrl.title',
|
||||
defaultMessage: 'Web URL',
|
||||
description: 'Title for web url section in info modal',
|
||||
},
|
||||
lockFileTitle: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.lockFile.title',
|
||||
defaultMessage: 'Lock file',
|
||||
},
|
||||
lockFileTooltipContent: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.lockFile.tooltip.content',
|
||||
defaultMessage: `By default, anyone can access a file you upload if
|
||||
they know the web URL, even if they are not enrolled in your course.
|
||||
You can prevent outside access to a file by locking the file. When
|
||||
you lock a file, the web URL only allows learners who are enrolled
|
||||
in your course and signed in to access the file.`,
|
||||
description: 'Label for lock file checkbox in info modal',
|
||||
},
|
||||
activeCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.activeCheckbox.label',
|
||||
defaultMessage: 'Active',
|
||||
description: 'Label for active checkbox in filter section of sort and filter modal',
|
||||
},
|
||||
inactiveCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.inactiveCheckbox.label',
|
||||
defaultMessage: 'Inactive',
|
||||
description: 'Label for inactive checkbox in filter section of sort and filter modal',
|
||||
},
|
||||
lockedCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.lockedCheckbox.label',
|
||||
defaultMessage: 'Locked',
|
||||
description: 'Label for locked checkbox in filter section of sort and filter modal',
|
||||
},
|
||||
publicCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.publicCheckbox.label',
|
||||
defaultMessage: 'Public',
|
||||
description: 'Label for public checkbox in filter section of sort and filter modal',
|
||||
},
|
||||
codeCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.codeCheckbox.label',
|
||||
defaultMessage: 'Code',
|
||||
description: 'Label for code checkbox in filter section of sort and filter modal',
|
||||
},
|
||||
imageCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.imageCheckbox.label',
|
||||
defaultMessage: 'Images',
|
||||
description: 'Label for images checkbox in filter section of sort and filter modal',
|
||||
},
|
||||
documentCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.documentCheckbox.label',
|
||||
defaultMessage: 'Documents',
|
||||
description: 'Label for documents checkbox in filter section of sort and filter modal',
|
||||
},
|
||||
audioCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.audioCheckbox.label',
|
||||
defaultMessage: 'Audio',
|
||||
description: 'Label for audio checkbox in filter section of sort and filter modal',
|
||||
},
|
||||
otherCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.otherCheckbox.label',
|
||||
defaultMessage: 'Other',
|
||||
description: 'Label for other checkbox in filter section of sort and filter modal',
|
||||
},
|
||||
overwriteConfirmMessage: {
|
||||
id: 'course-authoring.files-and-videos.overwrite.modal.confirmation-message',
|
||||
|
||||
132
src/files-and-videos/generic/DeleteConfirmationModal.jsx
Normal file
132
src/files-and-videos/generic/DeleteConfirmationModal.jsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import {
|
||||
ActionRow,
|
||||
AlertModal,
|
||||
Button,
|
||||
Collapsible,
|
||||
Hyperlink,
|
||||
Truncate,
|
||||
} from '@openedx/paragon';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const DeleteConfirmationModal = ({
|
||||
isDeleteConfirmationOpen,
|
||||
closeDeleteConfirmation,
|
||||
handleBulkDelete,
|
||||
selectedRows,
|
||||
fileType,
|
||||
// injected
|
||||
intl,
|
||||
}) => {
|
||||
const firstSelectedRow = selectedRows[0]?.original;
|
||||
let activeContentRows = [];
|
||||
if (Array.isArray(selectedRows)) {
|
||||
activeContentRows = selectedRows.filter(row => row.original.activeStatus === 'active');
|
||||
}
|
||||
const isDeletingCourseContent = activeContentRows.length > 0;
|
||||
|
||||
const deletedCourseContent = activeContentRows.map(({ original }) => (
|
||||
<li style={{ listStyle: 'None' }} key={original.id}>
|
||||
<Collapsible
|
||||
styling="basic"
|
||||
title={(
|
||||
<h3 className="h5 m-n2">
|
||||
<Truncate lines={1}>
|
||||
{original.displayName}
|
||||
</Truncate>
|
||||
</h3>
|
||||
)}
|
||||
data-testid={`collapsible-${original.id}`}
|
||||
>
|
||||
<ul className="px-2 py-0">
|
||||
{original.usageLocations.map(location => (
|
||||
<li key={`usage-location-${location.displayLocation}`} style={{ listStyle: 'square' }}>
|
||||
<Hyperlink destination={`${getConfig().STUDIO_BASE_URL}${location.url}`} target="_blank">
|
||||
{location.displayLocation}
|
||||
</Hyperlink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Collapsible>
|
||||
</li>
|
||||
));
|
||||
|
||||
return (
|
||||
<AlertModal
|
||||
className="small"
|
||||
title={intl.formatMessage(
|
||||
messages.deleteConfirmationTitle,
|
||||
{
|
||||
fileName: firstSelectedRow?.displayName,
|
||||
fileNumber: selectedRows.length,
|
||||
fileType,
|
||||
},
|
||||
)}
|
||||
isOpen={isDeleteConfirmationOpen}
|
||||
onClose={closeDeleteConfirmation}
|
||||
footerNode={(
|
||||
<ActionRow>
|
||||
<Button variant="tertiary" onClick={closeDeleteConfirmation}>
|
||||
{intl.formatMessage(messages.cancelButtonLabel)}
|
||||
</Button>
|
||||
<Button onClick={handleBulkDelete}>
|
||||
{intl.formatMessage(messages.deleteFileButtonLabel)}
|
||||
</Button>
|
||||
</ActionRow>
|
||||
)}
|
||||
>
|
||||
{intl.formatMessage(
|
||||
messages.deleteConfirmationMessage,
|
||||
{
|
||||
fileName: firstSelectedRow?.displayName,
|
||||
fileNumber: selectedRows.length,
|
||||
fileType,
|
||||
},
|
||||
)}
|
||||
{isDeletingCourseContent && (
|
||||
<div className="mt-3">
|
||||
{intl.formatMessage(
|
||||
messages.deleteConfirmationUsageMessage,
|
||||
{
|
||||
fileNumber: activeContentRows.length,
|
||||
fileType,
|
||||
},
|
||||
)}
|
||||
<ul className="p-0">
|
||||
{deletedCourseContent}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</AlertModal>
|
||||
);
|
||||
};
|
||||
|
||||
DeleteConfirmationModal.defaultProps = {
|
||||
selectedRows: [],
|
||||
};
|
||||
|
||||
DeleteConfirmationModal.propTypes = {
|
||||
selectedRows: PropTypes.arrayOf(PropTypes.shape({
|
||||
original: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
activeStatus: PropTypes.string,
|
||||
usageLocations: PropTypes.arrayOf(PropTypes.shape({
|
||||
url: PropTypes.string,
|
||||
displayLocation: PropTypes.string,
|
||||
})),
|
||||
}),
|
||||
})),
|
||||
isDeleteConfirmationOpen: PropTypes.bool.isRequired,
|
||||
closeDeleteConfirmation: PropTypes.func.isRequired,
|
||||
handleBulkDelete: PropTypes.func.isRequired,
|
||||
fileType: PropTypes.string.isRequired,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(DeleteConfirmationModal);
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
within,
|
||||
} from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import DeleteConfirmationModal from './DeleteConfirmationModal';
|
||||
|
||||
const defaultProps = {
|
||||
isDeleteConfirmationOpen: true,
|
||||
closeDeleteConfirmation: jest.fn(),
|
||||
handleBulkDelete: jest.fn(),
|
||||
selectedRows: [{
|
||||
original: {
|
||||
displayName: 'test',
|
||||
activeStatus: 'active',
|
||||
id: 'file-test',
|
||||
usageLocations: [{
|
||||
displayLocation: 'unit', url: 'unit/url',
|
||||
}],
|
||||
},
|
||||
}],
|
||||
fileType: 'file',
|
||||
};
|
||||
|
||||
const renderComponent = (props) => {
|
||||
render(
|
||||
<IntlProvider locale="en">
|
||||
<DeleteConfirmationModal {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
describe('DeleteConfirmationModal', () => {
|
||||
it('should show file name in title', () => {
|
||||
renderComponent(defaultProps);
|
||||
const { displayName } = defaultProps.selectedRows[0].original;
|
||||
|
||||
expect(screen.getByText(`Delete ${displayName}`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show number of files in title', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
selectedRows: [
|
||||
...defaultProps.selectedRows,
|
||||
{ original: { displayName: 'test 2', activeStatus: 'inactive' } },
|
||||
],
|
||||
};
|
||||
|
||||
renderComponent(props);
|
||||
const numberOfFiles = props.selectedRows.length;
|
||||
|
||||
expect(screen.getByText(`Delete ${numberOfFiles} ${defaultProps.fileType}s`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show delete confirmation usage list', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
selectedRows: [],
|
||||
};
|
||||
renderComponent(props);
|
||||
|
||||
expect(screen.queryByRole('list')).toBeNull();
|
||||
});
|
||||
|
||||
it('should show test file in delete confirmation usage list', () => {
|
||||
renderComponent(defaultProps);
|
||||
const deleteUsageList = screen.getByRole('list');
|
||||
|
||||
expect(within(deleteUsageList).getByTestId('collapsible-file-test')).toBeVisible();
|
||||
});
|
||||
|
||||
it('should not show test file in delete confirmation usage list', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
selectedRows: [
|
||||
...defaultProps.selectedRows,
|
||||
{ original: { displayName: 'test 2', activeStatus: 'inactive', id: 'file-test2' } },
|
||||
],
|
||||
};
|
||||
renderComponent(props);
|
||||
const deleteUsageList = screen.getByRole('list');
|
||||
|
||||
expect(within(deleteUsageList).queryByTestId('collapsible-file-test2')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -3,14 +3,11 @@ import PropTypes from 'prop-types';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
DataTable,
|
||||
TextFilter,
|
||||
Dropzone,
|
||||
CardView,
|
||||
DataTable,
|
||||
Dropzone,
|
||||
TextFilter,
|
||||
useToggle,
|
||||
AlertModal,
|
||||
ActionRow,
|
||||
Button,
|
||||
} from '@openedx/paragon';
|
||||
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
@@ -28,6 +25,7 @@ import {
|
||||
Footer,
|
||||
} from './table-components';
|
||||
import ApiStatusToast from './ApiStatusToast';
|
||||
import DeleteConfirmationModal from './DeleteConfirmationModal';
|
||||
|
||||
const FileTable = ({
|
||||
files,
|
||||
@@ -275,23 +273,15 @@ const FileTable = ({
|
||||
sidebar={infoModalSidebar}
|
||||
/>
|
||||
)}
|
||||
<AlertModal
|
||||
title={intl.formatMessage(messages.deleteConfirmationTitle, { fileType })}
|
||||
isOpen={isDeleteConfirmationOpen}
|
||||
onClose={closeDeleteConfirmation}
|
||||
footerNode={(
|
||||
<ActionRow>
|
||||
<Button variant="tertiary" onClick={closeDeleteConfirmation}>
|
||||
{intl.formatMessage(messages.cancelButtonLabel)}
|
||||
</Button>
|
||||
<Button onClick={handleBulkDelete}>
|
||||
{intl.formatMessage(messages.deleteFileButtonLabel)}
|
||||
</Button>
|
||||
</ActionRow>
|
||||
)}
|
||||
>
|
||||
{intl.formatMessage(messages.deleteConfirmationMessage, { fileNumber: selectedRows.length, fileType })}
|
||||
</AlertModal>
|
||||
<DeleteConfirmationModal
|
||||
{...{
|
||||
isDeleteConfirmationOpen,
|
||||
closeDeleteConfirmation,
|
||||
handleBulkDelete,
|
||||
selectedRows,
|
||||
fileType,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,146 +4,200 @@ const messages = defineMessages({
|
||||
rowStatusMessage: {
|
||||
id: 'course-authoring.files-and-upload.rowStatus.message',
|
||||
defaultMessage: 'Showing {fileCount} of {rowCount}',
|
||||
description: 'This message is showed to notify user of the number of files being shown',
|
||||
},
|
||||
apiStatusToastMessage: {
|
||||
id: 'course-authoring.files-and-upload.apiStatus.message',
|
||||
defaultMessage: '{actionType} {selectedRowCount} {fileType}(s)',
|
||||
description: 'This message is showed in the toast when action is applied to files',
|
||||
},
|
||||
apiStatusAddingAction: {
|
||||
id: 'course-authoring.files-and-upload.apiStatus.addingAction.message',
|
||||
defaultMessage: 'Adding',
|
||||
description: 'This message is used in the toast when files are added',
|
||||
},
|
||||
apiStatusDeletingAction: {
|
||||
id: 'course-authoring.files-and-upload.apiStatus.deletingAction.message',
|
||||
defaultMessage: 'Deleting',
|
||||
description: 'This message is used in the toast when files are deleted',
|
||||
},
|
||||
apiStatusDownloadingAction: {
|
||||
id: 'course-authoring.files-and-upload.apiStatus.downloadingAction.message',
|
||||
defaultMessage: 'Downloading',
|
||||
description: 'This message is used in the toast when files are downloaded',
|
||||
},
|
||||
fileSizeError: {
|
||||
id: 'course-authoring.files-and-upload.addFiles.error.fileSize',
|
||||
defaultMessage: 'Uploaded file(s) must be 20 MB or less. Please resize file(s) and try again.',
|
||||
description: 'This error message is shown when user tries to upload a file larger than 20 MB',
|
||||
},
|
||||
noResultsFoundMessage: {
|
||||
id: 'course-authoring.files-and-upload.table.noResultsFound.message',
|
||||
defaultMessage: 'No results found',
|
||||
description: 'This message is shown when no files are found based on name search',
|
||||
},
|
||||
addFilesButtonLabel: {
|
||||
id: 'course-authoring.files-and-upload.addFiles.button.label',
|
||||
defaultMessage: 'Add {fileType}s',
|
||||
description: 'Label for add files button, name changes based on page',
|
||||
},
|
||||
actionsButtonLabel: {
|
||||
id: 'course-authoring.files-and-upload.action.button.label',
|
||||
defaultMessage: 'Actions',
|
||||
description: 'Label for actions dropdown button',
|
||||
},
|
||||
errorAlertMessage: {
|
||||
id: 'course-authoring.files-and-upload.errorAlert.message',
|
||||
defaultMessage: '{message}',
|
||||
description: 'Message shell for error alert',
|
||||
},
|
||||
transcriptionErrorMessage: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.transcripts.error.alert',
|
||||
defaultMessage: 'Transcript failed: "{error}"',
|
||||
description: 'Message for transcript error in info modal',
|
||||
},
|
||||
usageTitle: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.usage.title',
|
||||
defaultMessage: 'Usage',
|
||||
description: 'Title for usage information section in info modal',
|
||||
},
|
||||
usageLoadingMessage: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.usage.loading.message',
|
||||
defaultMessage: 'Loading',
|
||||
description: 'Screen reader text for loading spinner in usage information section',
|
||||
},
|
||||
usageNotInUseMessage: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.usage.notInUse.message',
|
||||
defaultMessage: 'Currently not in use',
|
||||
description: 'Message for usage information section when file is not used in course',
|
||||
},
|
||||
copyVideoIdTitle: {
|
||||
id: 'course-authoring.files-and-uploads.cardMenu.copyVideoIdTitle',
|
||||
defaultMessage: 'Copy video ID',
|
||||
description: 'Label for copy video id button in card menu dropdown',
|
||||
},
|
||||
copyStudioUrlTitle: {
|
||||
id: 'course-authoring.files-and-uploads.cardMenu.copyStudioUrlTitle',
|
||||
defaultMessage: 'Copy Studio Url',
|
||||
description: 'Label for copy studio url button in card menu dropdown',
|
||||
},
|
||||
copyWebUrlTitle: {
|
||||
id: 'course-authoring.files-and-uploads.cardMenu.copyWebUrlTitle',
|
||||
defaultMessage: 'Copy Web Url',
|
||||
description: 'Label for copy web url button in card menu dropdown',
|
||||
},
|
||||
downloadTitle: {
|
||||
id: 'course-authoring.files-and-uploads.cardMenu.downloadTitle',
|
||||
defaultMessage: 'Download',
|
||||
description: 'Label for download button in card menu dropdown',
|
||||
},
|
||||
lockMenuTitle: {
|
||||
id: 'course-authoring.files-and-uploads.cardMenu.lockTitle',
|
||||
defaultMessage: 'Lock',
|
||||
description: 'Label for lock button in card menu dropdown',
|
||||
},
|
||||
lockFileTooltipContent: {
|
||||
id: 'course-authoring.files-and-uploads.file-info.lockFile.tooltip.content',
|
||||
defaultMessage: `By default, anyone can access a file you upload if
|
||||
they know the web URL, even if they are not enrolled in your course.
|
||||
You can prevent outside access to a file by locking the file. When
|
||||
you lock a file, the web URL only allows learners who are enrolled
|
||||
in your course and signed in to access the file.`,
|
||||
description: 'Tooltip message for the lock icon in the table view of files',
|
||||
},
|
||||
unlockMenuTitle: {
|
||||
id: 'course-authoring.files-and-uploads.cardMenu.unlockTitle',
|
||||
defaultMessage: 'Unlock',
|
||||
description: 'Label for unlock button in card menu dropdown',
|
||||
},
|
||||
infoTitle: {
|
||||
id: 'course-authoring.files-and-uploads.cardMenu.infoTitle',
|
||||
defaultMessage: 'Info',
|
||||
description: 'Label for info button in card menu dropdown',
|
||||
},
|
||||
downloadEncodingsTitle: {
|
||||
id: 'course-authoring.files-and-uploads.cardMenu.downloadEncodingsTitle',
|
||||
defaultMessage: 'Download video list (.csv)',
|
||||
description: 'Label for download video list button in actions dropdown',
|
||||
},
|
||||
deleteTitle: {
|
||||
id: 'course-authoring.files-and-uploads.cardMenu.deleteTitle',
|
||||
defaultMessage: 'Delete',
|
||||
description: 'Label for delete button in card menu dropdown',
|
||||
},
|
||||
deleteConfirmationTitle: {
|
||||
id: 'course-authoring.files-and-uploads..deleteConfirmation.title',
|
||||
defaultMessage: 'Delete {fileType}(s) confirmation',
|
||||
defaultMessage: 'Delete {fileNumber, plural, one {{fileName}} other {{fileNumber} {fileType}s}}',
|
||||
description: 'Title for delete confirmation modal',
|
||||
},
|
||||
deleteConfirmationMessage: {
|
||||
id: 'course-authoring.files-and-uploads..deleteConfirmation.message',
|
||||
defaultMessage: 'Are you sure you want to delete {fileNumber} {fileType}(s)? This action cannot be undone.',
|
||||
defaultMessage: `
|
||||
Are you sure you want to delete {fileNumber, plural, one {{fileName}} other {{fileNumber} {fileType}s}}?
|
||||
This action cannot be undone and may break your course if the {fileNumber, plural, one {{fileType} is} other {{fileType}s are}}
|
||||
used in the course content, advanced settings, updates, or schedule and details.
|
||||
`,
|
||||
description: 'Message presented to user listing the number of files they are attempting to delete in the delete confirmation modal',
|
||||
},
|
||||
deleteConfirmationUsageMessage: {
|
||||
id: 'course-authoring.files-and-uploads..deleteConfirmation.message',
|
||||
defaultMessage: 'The following {fileNumber, plural, one {{fileType} is} other {{fileType}s are}} used in course content. Consider updating the content before deleting.',
|
||||
description: 'Message listing where the files the user is attempting to delete are used in the course',
|
||||
},
|
||||
deleteFileButtonLabel: {
|
||||
id: 'course-authoring.files-and-uploads.deleteConfirmation.deleteFile.label',
|
||||
defaultMessage: 'Delete',
|
||||
description: 'Label for delete button in delete confirmation modal modal',
|
||||
},
|
||||
cancelButtonLabel: {
|
||||
id: 'course-authoring.files-and-uploads.cancelButton.label',
|
||||
defaultMessage: 'Cancel',
|
||||
description: 'Label for cancel button in modals',
|
||||
},
|
||||
sortButtonLabel: {
|
||||
id: 'course-authoring.files-and-uploads.sortButton.label',
|
||||
defaultMessage: 'Sort and filter',
|
||||
description: 'Label for button that opens the sort and filter modal',
|
||||
},
|
||||
sortModalTitleLabel: {
|
||||
id: 'course-authoring.files-and-uploads.sortModal.title',
|
||||
defaultMessage: 'Sort by',
|
||||
description: 'Title for Sort By secition in sort and filter modal',
|
||||
},
|
||||
sortByNameAscending: {
|
||||
id: 'course-authoring.files-and-uploads.sortByNameAscendingButton.label',
|
||||
defaultMessage: 'Name (A-Z)',
|
||||
description: 'Label for ascending name radio button in sort and filter modal',
|
||||
},
|
||||
sortByNewest: {
|
||||
id: 'course-authoring.files-and-uploads.sortByNewestButton.label',
|
||||
defaultMessage: 'Newest',
|
||||
description: 'Label for descending date added radio button in sort and filter modal',
|
||||
},
|
||||
sortBySizeDescending: {
|
||||
id: 'course-authoring.files-and-uploads.sortBySizeDescendingButton.label',
|
||||
defaultMessage: 'File size (High to low)',
|
||||
description: 'Label for descending file size radio button in sort and filter modal',
|
||||
},
|
||||
sortByNameDescending: {
|
||||
id: 'course-authoring.files-and-uploads.sortByNameDescendingButton.label',
|
||||
defaultMessage: 'Name (Z-A)',
|
||||
description: 'Label for descending name radio button in sort and filter modal',
|
||||
},
|
||||
sortByOldest: {
|
||||
id: 'course-authoring.files-and-uploads.sortByOldestButton.label',
|
||||
defaultMessage: 'Oldest',
|
||||
description: 'Label for ascending date added radio button in sort and filter modal',
|
||||
},
|
||||
sortBySizeAscending: {
|
||||
id: 'course-authoring.files-and-uploads.sortBySizeAscendingButton.label',
|
||||
defaultMessage: 'File size(Low to high)',
|
||||
description: 'Label for ascending file size radio button in sort and filter modal',
|
||||
},
|
||||
applySortButton: {
|
||||
id: 'course-authoring.files-and-uploads.applyySortButton.label',
|
||||
defaultMessage: 'Apply',
|
||||
description: 'Label for apply sort button in sort and filter modal',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ describe('Videos page', () => {
|
||||
axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(204);
|
||||
|
||||
fireEvent.click(deleteButton);
|
||||
expect(screen.getByText('Delete video(s) confirmation')).toBeVisible();
|
||||
expect(screen.getByText('Delete mOckID1.mp4')).toBeVisible();
|
||||
await act(async () => {
|
||||
userEvent.click(deleteButton);
|
||||
});
|
||||
@@ -287,7 +287,7 @@ describe('Videos page', () => {
|
||||
userEvent.click(confirmDeleteButton);
|
||||
});
|
||||
|
||||
expect(screen.queryByText('Delete video(s) confirmation')).toBeNull();
|
||||
expect(screen.queryByText('Delete mOckID1.mp4')).toBeNull();
|
||||
|
||||
// Check if the video is deleted in the store and UI
|
||||
const deleteStatus = store.getState().videos.deletingStatus;
|
||||
@@ -541,10 +541,10 @@ describe('Videos page', () => {
|
||||
axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(204);
|
||||
fireEvent.click(within(fileMenuButton).getByLabelText('file-menu-toggle'));
|
||||
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
|
||||
expect(screen.getByText('Delete video(s) confirmation')).toBeVisible();
|
||||
expect(screen.getByText('Delete mOckID1.mp4')).toBeVisible();
|
||||
|
||||
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
|
||||
expect(screen.queryByText('Delete video(s) confirmation')).toBeNull();
|
||||
expect(screen.queryByText('Delete mOckID1.mp4')).toBeNull();
|
||||
|
||||
executeThunk(deleteVideoFile(courseId, 'mOckID1', 5), store.dispatch);
|
||||
});
|
||||
@@ -640,10 +640,10 @@ describe('Videos page', () => {
|
||||
axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(404);
|
||||
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
|
||||
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
|
||||
expect(screen.getByText('Delete video(s) confirmation')).toBeVisible();
|
||||
expect(screen.getByText('Delete mOckID1.mp4')).toBeVisible();
|
||||
|
||||
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
|
||||
expect(screen.queryByText('Delete video(s) confirmation')).toBeNull();
|
||||
expect(screen.queryByText('Delete mOckID1.mp4')).toBeNull();
|
||||
|
||||
executeThunk(deleteVideoFile(courseId, 'mOckID1', 5), store.dispatch);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user