fix: files page timeout (#749)
This commit is contained in:
@@ -12,6 +12,7 @@ export const RequestStatus = {
|
||||
DENIED: 'denied',
|
||||
PENDING: 'pending',
|
||||
CLEAR: 'clear',
|
||||
PARTIAL: 'partial',
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -167,6 +167,7 @@ const FilesPage = ({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FilesPageProvider courseId={courseId}>
|
||||
<Container size="xl" className="p-4 pt-4.5">
|
||||
@@ -176,28 +177,31 @@ const FilesPage = ({
|
||||
addFileStatus={addAssetStatus}
|
||||
deleteFileStatus={deleteAssetStatus}
|
||||
updateFileStatus={updateAssetStatus}
|
||||
loadingStatus={loadingStatus}
|
||||
/>
|
||||
<div className="h2">
|
||||
<FormattedMessage {...messages.heading} />
|
||||
</div>
|
||||
<FileTable
|
||||
{...{
|
||||
courseId,
|
||||
data,
|
||||
handleAddFile,
|
||||
handleDeleteFile,
|
||||
handleDownloadFile,
|
||||
handleLockFile,
|
||||
handleUsagePaths,
|
||||
handleErrorReset,
|
||||
handleFileOrder,
|
||||
tableColumns,
|
||||
maxFileSize,
|
||||
thumbnailPreview,
|
||||
infoModalSidebar,
|
||||
files: assets,
|
||||
}}
|
||||
/>
|
||||
{loadingStatus !== RequestStatus.FAILED && (
|
||||
<FileTable
|
||||
{...{
|
||||
courseId,
|
||||
data,
|
||||
handleAddFile,
|
||||
handleDeleteFile,
|
||||
handleDownloadFile,
|
||||
handleLockFile,
|
||||
handleUsagePaths,
|
||||
handleErrorReset,
|
||||
handleFileOrder,
|
||||
tableColumns,
|
||||
maxFileSize,
|
||||
thumbnailPreview,
|
||||
infoModalSidebar,
|
||||
files: assets,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
</FilesPageProvider>
|
||||
);
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
getStatusValue,
|
||||
courseId,
|
||||
initialState,
|
||||
generateNextPageResponse,
|
||||
} from './factories/mockApiResponses';
|
||||
|
||||
import {
|
||||
@@ -57,15 +58,22 @@ const renderComponent = () => {
|
||||
|
||||
const mockStore = async (
|
||||
status,
|
||||
skipNextPageFetch,
|
||||
) => {
|
||||
const fetchAssetsUrl = `${getAssetsUrl(courseId)}?page_size=50`;
|
||||
const fetchAssetsUrl = `${getAssetsUrl(courseId)}?page=0`;
|
||||
axiosMock.onGet(fetchAssetsUrl).reply(getStatusValue(status), generateFetchAssetApiResponse());
|
||||
if (!skipNextPageFetch) {
|
||||
const nextPageUrl = `${getAssetsUrl(courseId)}?page=1`;
|
||||
axiosMock.onGet(nextPageUrl).reply(getStatusValue(status), generateNextPageResponse());
|
||||
}
|
||||
renderComponent();
|
||||
await executeThunk(fetchAssets(courseId), store.dispatch);
|
||||
};
|
||||
|
||||
const emptyMockStore = async (status) => {
|
||||
const fetchAssetsUrl = getAssetsUrl(courseId);
|
||||
const fetchAssetsUrl = `${getAssetsUrl(courseId)}?page=0`;
|
||||
axiosMock.onGet(fetchAssetsUrl).reply(getStatusValue(status), generateEmptyApiResponse());
|
||||
renderComponent();
|
||||
await executeThunk(fetchAssets(courseId), store.dispatch);
|
||||
};
|
||||
|
||||
@@ -93,27 +101,26 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should return placeholder component', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.DENIED);
|
||||
|
||||
expect(screen.getByTestId('under-construction-placeholder')).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have Files title', async () => {
|
||||
renderComponent();
|
||||
await emptyMockStore(RequestStatus.SUCCESSFUL);
|
||||
|
||||
expect(screen.getByText('Files')).toBeVisible();
|
||||
});
|
||||
|
||||
it('should render dropzone', async () => {
|
||||
renderComponent();
|
||||
await emptyMockStore(RequestStatus.SUCCESSFUL);
|
||||
|
||||
expect(screen.getByTestId('files-dropzone')).toBeVisible();
|
||||
|
||||
expect(screen.queryByTestId('files-data-table')).toBeNull();
|
||||
});
|
||||
|
||||
it('should upload a single file', async () => {
|
||||
renderComponent();
|
||||
await emptyMockStore(RequestStatus.SUCCESSFUL);
|
||||
const dropzone = screen.getByTestId('files-dropzone');
|
||||
await act(async () => {
|
||||
@@ -154,19 +161,22 @@ describe('FilesAndUploads', () => {
|
||||
|
||||
describe('table view', () => {
|
||||
it('should render table with gallery card', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
|
||||
expect(screen.getByTestId('files-data-table')).toBeVisible();
|
||||
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByTestId('grid-card-mOckID1')[0]).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
it('should switch table to list view', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('files-data-table')).toBeVisible();
|
||||
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByTestId('grid-card-mOckID1')[0]).toBeVisible();
|
||||
});
|
||||
|
||||
expect(screen.queryByRole('table')).toBeNull();
|
||||
|
||||
@@ -182,10 +192,12 @@ describe('FilesAndUploads', () => {
|
||||
|
||||
describe('table actions', () => {
|
||||
it('should upload a single file', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
axiosMock.onPost(getAssetsUrl(courseId)).reply(200, generateNewAssetApiResponse());
|
||||
const addFilesButton = screen.getByLabelText('file-input');
|
||||
let addFilesButton;
|
||||
await waitFor(() => {
|
||||
addFilesButton = screen.getByLabelText('file-input');
|
||||
});
|
||||
await act(async () => {
|
||||
userEvent.upload(addFilesButton, file);
|
||||
await executeThunk(addAssetFile(courseId, file, 1), store.dispatch);
|
||||
@@ -195,12 +207,11 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should have disabled action buttons', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
|
||||
expect(actionsButton).toBeVisible();
|
||||
let actionsButton;
|
||||
|
||||
await waitFor(() => {
|
||||
actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
|
||||
fireEvent.click(actionsButton);
|
||||
});
|
||||
expect(screen.getByText(messages.downloadTitle.defaultMessage).closest('a')).toHaveClass('disabled');
|
||||
@@ -209,10 +220,14 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('delete button should be enabled and delete selected file', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const selectCardButton = screen.getAllByTestId('datatable-select-column-checkbox-cell')[0];
|
||||
fireEvent.click(selectCardButton);
|
||||
let selectCardButton;
|
||||
|
||||
await waitFor(() => {
|
||||
[selectCardButton] = screen.getAllByTestId('datatable-select-column-checkbox-cell');
|
||||
fireEvent.click(selectCardButton);
|
||||
});
|
||||
|
||||
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
|
||||
expect(actionsButton).toBeVisible();
|
||||
|
||||
@@ -248,7 +263,6 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('download button should be enabled and download single selected file', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const selectCardButton = screen.getAllByTestId('datatable-select-column-checkbox-cell')[0];
|
||||
fireEvent.click(selectCardButton);
|
||||
@@ -266,7 +280,6 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('download button should be enabled and download multiple selected files', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const selectCardButtons = screen.getAllByTestId('datatable-select-column-checkbox-cell');
|
||||
fireEvent.click(selectCardButtons[0]);
|
||||
@@ -288,7 +301,6 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('sort button should be enabled and sort files by name', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const sortsButton = screen.getByText(messages.sortButtonLabel.defaultMessage);
|
||||
expect(sortsButton).toBeVisible();
|
||||
@@ -309,7 +321,6 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('sort button should be enabled and sort files by file size', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const sortsButton = screen.getByText(messages.sortButtonLabel.defaultMessage);
|
||||
expect(sortsButton).toBeVisible();
|
||||
@@ -332,12 +343,12 @@ describe('FilesAndUploads', () => {
|
||||
|
||||
describe('card menu actions', () => {
|
||||
it('should open asset info', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
let assetMenuButton;
|
||||
|
||||
const assetMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
|
||||
expect(assetMenuButton).toBeVisible();
|
||||
await waitFor(() => {
|
||||
[assetMenuButton] = screen.getAllByTestId('file-menu-dropdown-mOckID1');
|
||||
});
|
||||
|
||||
axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID1/usage`)
|
||||
.reply(201, {
|
||||
@@ -365,11 +376,8 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should open asset info and handle lock checkbox', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
const assetMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
|
||||
expect(assetMenuButton).toBeVisible();
|
||||
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID1')[0];
|
||||
|
||||
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID1`).reply(201, { locked: false });
|
||||
axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID1/usage`).reply(201, { usage_locations: { mOckID1: [] } });
|
||||
@@ -397,12 +405,9 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should unlock asset', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
|
||||
const assetMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
|
||||
expect(assetMenuButton).toBeVisible();
|
||||
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID1')[0];
|
||||
|
||||
await waitFor(() => {
|
||||
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID1`).reply(201, { locked: false });
|
||||
@@ -419,12 +424,9 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should lock asset', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID3')).toBeVisible();
|
||||
|
||||
const assetMenuButton = screen.getByTestId('file-menu-dropdown-mOckID3');
|
||||
expect(assetMenuButton).toBeVisible();
|
||||
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID3')[0];
|
||||
|
||||
await waitFor(() => {
|
||||
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID3`).reply(201, { locked: true });
|
||||
@@ -441,12 +443,9 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('download button should download file', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
|
||||
const assetMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
|
||||
expect(assetMenuButton).toBeVisible();
|
||||
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID1')[0];
|
||||
|
||||
await waitFor(() => {
|
||||
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
|
||||
@@ -456,12 +455,9 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('delete button should delete file', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
|
||||
const assetMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
|
||||
expect(assetMenuButton).toBeVisible();
|
||||
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID1')[0];
|
||||
|
||||
await waitFor(() => {
|
||||
axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID1`).reply(204);
|
||||
@@ -482,15 +478,36 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
describe('api errors', () => {
|
||||
it('404 intitial fetch should show error', async () => {
|
||||
await mockStore(RequestStatus.FAILED);
|
||||
const { loadingStatus } = store.getState().assets;
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Error')).toBeVisible();
|
||||
});
|
||||
|
||||
expect(loadingStatus).toEqual(RequestStatus.FAILED);
|
||||
expect(screen.getByText('Failed to load all files.')).toBeVisible();
|
||||
});
|
||||
|
||||
it('404 intitial fetch should show error', async () => {
|
||||
await mockStore(RequestStatus.SUCCESSFUL, true);
|
||||
const { loadingStatus } = store.getState().assets;
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Error')).toBeVisible();
|
||||
});
|
||||
|
||||
expect(loadingStatus).toEqual(RequestStatus.PARTIAL);
|
||||
expect(screen.getByText('Failed to load remaining files.')).toBeVisible();
|
||||
});
|
||||
|
||||
it('invalid file size should show error', async () => {
|
||||
const errorMessage = 'File download.png exceeds maximum size of 20 MB.';
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
|
||||
axiosMock.onPost(getAssetsUrl(courseId)).reply(413, { error: errorMessage });
|
||||
const addFilesButton = screen.getByLabelText('file-input');
|
||||
await act(async () => {
|
||||
userEvent.upload(addFilesButton, file);
|
||||
await executeThunk(addAssetFile(courseId, file, 1), store.dispatch);
|
||||
});
|
||||
const addStatus = store.getState().assets.addingStatus;
|
||||
expect(addStatus).toEqual(RequestStatus.FAILED);
|
||||
@@ -499,7 +516,6 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('404 upload should show error', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
axiosMock.onPost(getAssetsUrl(courseId)).reply(404);
|
||||
const addFilesButton = screen.getByLabelText('file-input');
|
||||
@@ -514,15 +530,12 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('404 delete should show error', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
|
||||
const assetMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
|
||||
expect(assetMenuButton).toBeVisible();
|
||||
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID3')[0];
|
||||
|
||||
await waitFor(() => {
|
||||
axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID1`).reply(404);
|
||||
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();
|
||||
@@ -530,23 +543,20 @@ describe('FilesAndUploads', () => {
|
||||
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
|
||||
expect(screen.queryByText('Delete file(s) confirmation')).toBeNull();
|
||||
|
||||
executeThunk(deleteAssetFile(courseId, 'mOckID1', 5), store.dispatch);
|
||||
executeThunk(deleteAssetFile(courseId, 'mOckID3', 5), store.dispatch);
|
||||
});
|
||||
const deleteStatus = store.getState().assets.deletingStatus;
|
||||
expect(deleteStatus).toEqual(RequestStatus.FAILED);
|
||||
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
expect(screen.getAllByTestId('grid-card-mOckID3')[0]).toBeVisible();
|
||||
|
||||
expect(screen.getByText('Error')).toBeVisible();
|
||||
});
|
||||
|
||||
it('404 usage path fetch should show error', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID3')).toBeVisible();
|
||||
|
||||
const assetMenuButton = screen.getByTestId('file-menu-dropdown-mOckID3');
|
||||
expect(assetMenuButton).toBeVisible();
|
||||
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID3')[0];
|
||||
|
||||
axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID3/usage`).reply(404);
|
||||
await waitFor(() => {
|
||||
@@ -563,12 +573,9 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('404 lock update should show error', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID3')).toBeVisible();
|
||||
|
||||
const assetMenuButton = screen.getByTestId('file-menu-dropdown-mOckID3');
|
||||
expect(assetMenuButton).toBeVisible();
|
||||
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID3')[0];
|
||||
|
||||
await waitFor(() => {
|
||||
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID3`).reply(404);
|
||||
@@ -587,7 +594,6 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('multiple asset file fetch failure should show error', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const selectCardButtons = screen.getAllByTestId('datatable-select-column-checkbox-cell');
|
||||
fireEvent.click(selectCardButtons[0]);
|
||||
|
||||
@@ -17,10 +17,10 @@ export const getAssetsUrl = (courseId) => `${getApiBaseUrl()}/assets/${courseId}
|
||||
* @param {string} courseId
|
||||
* @returns {Promise<[{}]>}
|
||||
*/
|
||||
export async function getAssets(courseId, totalCount) {
|
||||
const pageCount = totalCount || 50;
|
||||
export async function getAssets(courseId, page) {
|
||||
const nextPage = page || 0;
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(`${getAssetsUrl(courseId)}?page_size=${pageCount}`);
|
||||
.get(`${getAssetsUrl(courseId)}?page=${nextPage}`);
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
import { RequestStatus } from '../../../data/constants';
|
||||
|
||||
@@ -18,10 +19,18 @@ const slice = createSlice({
|
||||
lock: [],
|
||||
download: [],
|
||||
usageMetrics: [],
|
||||
loading: '',
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
setAssetIds: (state, { payload }) => {
|
||||
if (isEmpty(state.assetIds)) {
|
||||
state.assetIds = payload.assetIds;
|
||||
} else {
|
||||
state.assetIds = [...state.assetIds, ...payload.assetIds];
|
||||
}
|
||||
},
|
||||
setSortedAssetIds: (state, { payload }) => {
|
||||
state.assetIds = payload.assetIds;
|
||||
},
|
||||
updateLoadingStatus: (state, { payload }) => {
|
||||
@@ -57,8 +66,12 @@ const slice = createSlice({
|
||||
},
|
||||
updateErrors: (state, { payload }) => {
|
||||
const { error, message } = payload;
|
||||
const currentErrorState = state.errors[error];
|
||||
state.errors[error] = [...currentErrorState, message];
|
||||
if (error === 'loading') {
|
||||
state.errors.loading = message;
|
||||
} else {
|
||||
const currentErrorState = state.errors[error];
|
||||
state.errors[error] = [...currentErrorState, message];
|
||||
}
|
||||
},
|
||||
clearErrors: (state, { payload }) => {
|
||||
const { error } = payload;
|
||||
@@ -69,6 +82,7 @@ const slice = createSlice({
|
||||
|
||||
export const {
|
||||
setAssetIds,
|
||||
setSortedAssetIds,
|
||||
updateLoadingStatus,
|
||||
deleteAssetSuccess,
|
||||
addAssetSuccess,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from './api';
|
||||
import {
|
||||
setAssetIds,
|
||||
setSortedAssetIds,
|
||||
updateLoadingStatus,
|
||||
deleteAssetSuccess,
|
||||
addAssetSuccess,
|
||||
@@ -28,23 +29,51 @@ import {
|
||||
|
||||
import { updateFileValues } from './utils';
|
||||
|
||||
export function fetchAddtionalAsstets(courseId, totalCount) {
|
||||
return async (dispatch) => {
|
||||
let remainingAssetCount = totalCount;
|
||||
let page = 1;
|
||||
|
||||
/* eslint-disable no-await-in-loop */
|
||||
while (remainingAssetCount > 0) {
|
||||
try {
|
||||
const { assets } = await getAssets(courseId, page);
|
||||
const parsedAssets = updateFileValues(assets);
|
||||
dispatch(addModels({ modelType: 'assets', models: parsedAssets }));
|
||||
dispatch(setAssetIds({
|
||||
assetIds: assets.map(asset => asset.id),
|
||||
}));
|
||||
remainingAssetCount -= 50;
|
||||
page += 1;
|
||||
} catch (error) {
|
||||
remainingAssetCount = 0;
|
||||
dispatch(updateErrors({ error: 'loading', message: 'Failed to load remaining files.' }));
|
||||
dispatch(updateLoadingStatus({ status: RequestStatus.PARTIAL }));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAssets(courseId) {
|
||||
return async (dispatch) => {
|
||||
dispatch(updateLoadingStatus({ courseId, status: RequestStatus.IN_PROGRESS }));
|
||||
|
||||
try {
|
||||
const { totalCount } = await getAssets(courseId);
|
||||
const { assets } = await getAssets(courseId, totalCount);
|
||||
const { assets, totalCount } = await getAssets(courseId);
|
||||
const parsedAssets = updateFileValues(assets);
|
||||
dispatch(addModels({ modelType: 'assets', models: parsedAssets }));
|
||||
dispatch(setAssetIds({
|
||||
assetIds: assets.map(asset => asset.id),
|
||||
}));
|
||||
if (totalCount > 50) {
|
||||
dispatch(fetchAddtionalAsstets(courseId, totalCount - 50));
|
||||
}
|
||||
dispatch(updateLoadingStatus({ courseId, status: RequestStatus.SUCCESSFUL }));
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 403) {
|
||||
dispatch(updateLoadingStatus({ status: RequestStatus.DENIED }));
|
||||
} else {
|
||||
dispatch(updateErrors({ error: 'loading', message: 'Failed to load all files.' }));
|
||||
dispatch(updateLoadingStatus({ courseId, status: RequestStatus.FAILED }));
|
||||
}
|
||||
}
|
||||
@@ -54,7 +83,7 @@ export function fetchAssets(courseId) {
|
||||
export function updateAssetOrder(courseId, assetIds) {
|
||||
return async (dispatch) => {
|
||||
dispatch(updateLoadingStatus({ courseId, status: RequestStatus.IN_PROGRESS }));
|
||||
dispatch(setAssetIds({ assetIds }));
|
||||
dispatch(setSortedAssetIds({ assetIds }));
|
||||
dispatch(updateLoadingStatus({ courseId, status: RequestStatus.SUCCESSFUL }));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export const initialState = {
|
||||
lock: [],
|
||||
download: [],
|
||||
usageMetrics: [],
|
||||
loading: '',
|
||||
},
|
||||
},
|
||||
models: {
|
||||
@@ -106,13 +107,28 @@ export const generateFetchAssetApiResponse = () => ({
|
||||
thumbnail: null,
|
||||
},
|
||||
],
|
||||
totalCount: 50,
|
||||
totalCount: 51,
|
||||
});
|
||||
|
||||
export const generateEmptyApiResponse = () => ([{
|
||||
export const generateNextPageResponse = () => ({
|
||||
assets: [
|
||||
{
|
||||
id: 'mOckID6-3',
|
||||
displayName: 'mOckID6-3',
|
||||
locked: false,
|
||||
externalUrl: 'static_tab_1',
|
||||
portableUrl: 'May 17, 2023 at 22:08 UTC',
|
||||
contentType: 'application/octet-stream',
|
||||
dateAdded: '',
|
||||
thumbnail: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const generateEmptyApiResponse = () => ({
|
||||
assets: [],
|
||||
totalCount: 0,
|
||||
}]);
|
||||
});
|
||||
|
||||
export const generateNewAssetApiResponse = () => ({
|
||||
asset: {
|
||||
@@ -133,6 +149,8 @@ export const getStatusValue = (status) => {
|
||||
switch (status) {
|
||||
case RequestStatus.DENIED:
|
||||
return 403;
|
||||
case RequestStatus.FAILED:
|
||||
return 404;
|
||||
default:
|
||||
return 200;
|
||||
}
|
||||
|
||||
@@ -11,10 +11,18 @@ const EditFileErrors = ({
|
||||
addFileStatus,
|
||||
deleteFileStatus,
|
||||
updateFileStatus,
|
||||
loadingStatus,
|
||||
// injected
|
||||
intl,
|
||||
}) => (
|
||||
<>
|
||||
<ErrorAlert
|
||||
hideHeading={false}
|
||||
dismissError={() => resetErrors({ errorType: 'loading' })}
|
||||
isError={loadingStatus === RequestStatus.FAILED || loadingStatus === RequestStatus.PARTIAL}
|
||||
>
|
||||
{intl.formatMessage(messages.errorAlertMessage, { message: errorMessages.loading })}
|
||||
</ErrorAlert>
|
||||
<ErrorAlert
|
||||
hideHeading={false}
|
||||
dismissError={() => resetErrors({ errorType: 'add' })}
|
||||
@@ -75,10 +83,12 @@ EditFileErrors.propTypes = {
|
||||
lock: PropTypes.arrayOf(PropTypes.string),
|
||||
download: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
thumbnail: PropTypes.arrayOf(PropTypes.string),
|
||||
loading: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
addFileStatus: PropTypes.string.isRequired,
|
||||
deleteFileStatus: PropTypes.string.isRequired,
|
||||
updateFileStatus: PropTypes.string.isRequired,
|
||||
loadingStatus: PropTypes.string.isRequired,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
@@ -181,6 +181,7 @@ const VideosPage = ({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<VideosPageProvider courseId={courseId}>
|
||||
<Container size="xl" className="p-4 pt-4.5">
|
||||
@@ -190,6 +191,7 @@ const VideosPage = ({
|
||||
addFileStatus={addVideoStatus}
|
||||
deleteFileStatus={deleteVideoStatus}
|
||||
updateFileStatus={updateVideoStatus}
|
||||
loadingStatus={loadingStatus}
|
||||
/>
|
||||
<ActionRow>
|
||||
<div className="h2">
|
||||
@@ -209,35 +211,39 @@ const VideosPage = ({
|
||||
</Button>
|
||||
) : null}
|
||||
</ActionRow>
|
||||
{isVideoTranscriptEnabled ? (
|
||||
<TranscriptSettings
|
||||
{...{
|
||||
isTranscriptSettingsOpen,
|
||||
closeTranscriptSettings,
|
||||
handleErrorReset,
|
||||
errorMessages,
|
||||
transcriptStatus,
|
||||
courseId,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<FileTable
|
||||
{...{
|
||||
courseId,
|
||||
data,
|
||||
handleAddFile,
|
||||
handleDeleteFile,
|
||||
handleDownloadFile,
|
||||
handleUsagePaths,
|
||||
handleErrorReset,
|
||||
handleFileOrder,
|
||||
tableColumns,
|
||||
maxFileSize,
|
||||
thumbnailPreview,
|
||||
infoModalSidebar,
|
||||
files: videos,
|
||||
}}
|
||||
/>
|
||||
{loadingStatus !== RequestStatus.FAILED && (
|
||||
<>
|
||||
{isVideoTranscriptEnabled && (
|
||||
<TranscriptSettings
|
||||
{...{
|
||||
isTranscriptSettingsOpen,
|
||||
closeTranscriptSettings,
|
||||
handleErrorReset,
|
||||
errorMessages,
|
||||
transcriptStatus,
|
||||
courseId,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<FileTable
|
||||
{...{
|
||||
courseId,
|
||||
data,
|
||||
handleAddFile,
|
||||
handleDeleteFile,
|
||||
handleDownloadFile,
|
||||
handleUsagePaths,
|
||||
handleErrorReset,
|
||||
handleFileOrder,
|
||||
tableColumns,
|
||||
maxFileSize,
|
||||
thumbnailPreview,
|
||||
infoModalSidebar,
|
||||
files: videos,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
</VideosPageProvider>
|
||||
);
|
||||
|
||||
@@ -60,12 +60,14 @@ const mockStore = async (
|
||||
) => {
|
||||
const fetchVideosUrl = getVideosUrl(courseId);
|
||||
axiosMock.onGet(fetchVideosUrl).reply(getStatusValue(status), generateFetchVideosApiResponse());
|
||||
renderComponent();
|
||||
await executeThunk(fetchVideos(courseId), store.dispatch);
|
||||
};
|
||||
|
||||
const emptyMockStore = async (status) => {
|
||||
const fetchVideosUrl = getVideosUrl(courseId);
|
||||
axiosMock.onGet(fetchVideosUrl).reply(getStatusValue(status), generateEmptyApiResponse());
|
||||
renderComponent();
|
||||
await executeThunk(fetchVideos(courseId), store.dispatch);
|
||||
};
|
||||
|
||||
@@ -93,25 +95,21 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should return placeholder component', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.DENIED);
|
||||
expect(screen.getByTestId('under-construction-placeholder')).toBeVisible();
|
||||
});
|
||||
|
||||
it('should not render transcript settings button', async () => {
|
||||
renderComponent();
|
||||
await emptyMockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.queryByText(videoMessages.transcriptSettingsButtonLabel.defaultMessage));
|
||||
});
|
||||
|
||||
it('should have Video uploads title', async () => {
|
||||
renderComponent();
|
||||
await emptyMockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByText(videoMessages.heading.defaultMessage)).toBeVisible();
|
||||
});
|
||||
|
||||
it('should render dropzone', async () => {
|
||||
renderComponent();
|
||||
await emptyMockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('files-dropzone')).toBeVisible();
|
||||
|
||||
@@ -119,7 +117,6 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should upload a single file', async () => {
|
||||
renderComponent();
|
||||
await emptyMockStore(RequestStatus.SUCCESSFUL);
|
||||
const dropzone = screen.getByTestId('files-dropzone');
|
||||
await act(async () => {
|
||||
@@ -162,7 +159,6 @@ describe('FilesAndUploads', () => {
|
||||
|
||||
describe('table view', () => {
|
||||
it('should render transcript settings button', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const transcriptSettingsButton = screen.getByText(videoMessages.transcriptSettingsButtonLabel.defaultMessage);
|
||||
expect(transcriptSettingsButton).toBeVisible();
|
||||
@@ -175,7 +171,6 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should render table with gallery card', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('files-data-table')).toBeVisible();
|
||||
|
||||
@@ -183,7 +178,6 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should switch table to list view', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('files-data-table')).toBeVisible();
|
||||
|
||||
@@ -201,7 +195,6 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should update video thumbnail', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
axiosMock.onPost(`${getApiBaseUrl()}/video_images/${courseId}/mOckID1`).reply(200, { image_url: 'url' });
|
||||
const addThumbnailButton = screen.getByTestId('video-thumbnail-mOckID1');
|
||||
@@ -217,7 +210,6 @@ describe('FilesAndUploads', () => {
|
||||
|
||||
describe('table actions', () => {
|
||||
it('should upload a single file', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const mockResponseData = { status: '200', ok: true, blob: () => 'Data' };
|
||||
const mockFetchResponse = Promise.resolve(mockResponseData);
|
||||
@@ -236,10 +228,8 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should have disabled action buttons', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
|
||||
expect(actionsButton).toBeVisible();
|
||||
|
||||
await waitFor(() => {
|
||||
fireEvent.click(actionsButton);
|
||||
@@ -250,12 +240,10 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('delete button should be enabled and delete selected file', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const selectCardButton = screen.getAllByTestId('datatable-select-column-checkbox-cell')[0];
|
||||
fireEvent.click(selectCardButton);
|
||||
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
|
||||
expect(actionsButton).toBeVisible();
|
||||
|
||||
await waitFor(() => {
|
||||
fireEvent.click(actionsButton);
|
||||
@@ -289,12 +277,10 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('download button should be enabled and download single selected file', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const selectCardButton = screen.getAllByTestId('datatable-select-column-checkbox-cell')[0];
|
||||
fireEvent.click(selectCardButton);
|
||||
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
|
||||
expect(actionsButton).toBeVisible();
|
||||
|
||||
await waitFor(() => {
|
||||
fireEvent.click(actionsButton);
|
||||
@@ -311,13 +297,11 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('download button should be enabled and download multiple selected files', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const selectCardButtons = screen.getAllByTestId('datatable-select-column-checkbox-cell');
|
||||
fireEvent.click(selectCardButtons[0]);
|
||||
fireEvent.click(selectCardButtons[1]);
|
||||
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
|
||||
expect(actionsButton).toBeVisible();
|
||||
|
||||
await waitFor(() => {
|
||||
fireEvent.click(actionsButton);
|
||||
@@ -337,7 +321,6 @@ describe('FilesAndUploads', () => {
|
||||
|
||||
describe('Sort and filter button', () => {
|
||||
beforeEach(async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const sortAndFilterButton = screen.getByText(messages.sortButtonLabel.defaultMessage);
|
||||
|
||||
@@ -431,12 +414,9 @@ describe('FilesAndUploads', () => {
|
||||
describe('card menu actions', () => {
|
||||
describe('Info', () => {
|
||||
it('should open video info', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
|
||||
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
|
||||
expect(videoMenuButton).toBeVisible();
|
||||
|
||||
axiosMock.onGet(`${getVideosUrl(courseId)}/mOckID1/usage`)
|
||||
.reply(201, {
|
||||
@@ -460,11 +440,8 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should open video info modal and show info tab', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
|
||||
expect(videoMenuButton).toBeVisible();
|
||||
|
||||
axiosMock.onGet(`${getVideosUrl(courseId)}/mOckID1/usage`).reply(201, { usageLocations: [] });
|
||||
await waitFor(() => {
|
||||
@@ -481,11 +458,8 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should open video info modal and show transcript tab', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
|
||||
expect(videoMenuButton).toBeVisible();
|
||||
|
||||
axiosMock.onGet(`${getVideosUrl(courseId)}/mOckID1/usage`).reply(201, { usageLocations: [] });
|
||||
await waitFor(() => {
|
||||
@@ -505,7 +479,6 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('should show transcript error', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID3');
|
||||
|
||||
@@ -525,12 +498,9 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('download button should download file', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
|
||||
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
|
||||
expect(videoMenuButton).toBeVisible();
|
||||
|
||||
await waitFor(() => {
|
||||
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
|
||||
@@ -543,12 +513,9 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('delete button should delete file', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
|
||||
const fileMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
|
||||
expect(fileMenuButton).toBeVisible();
|
||||
|
||||
await waitFor(() => {
|
||||
axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(204);
|
||||
@@ -569,11 +536,20 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
describe('api errors', () => {
|
||||
it('404 intitial fetch should show error', async () => {
|
||||
await mockStore(RequestStatus.FAILED);
|
||||
|
||||
const { loadingStatus } = store.getState().videos;
|
||||
expect(loadingStatus).toEqual(RequestStatus.FAILED);
|
||||
|
||||
expect(screen.getByText('Error')).toBeVisible();
|
||||
});
|
||||
|
||||
it('invalid file size should show error', async () => {
|
||||
const errorMessage = 'File download.png exceeds maximum size of 5 GB.';
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
axiosMock.onPost(getCourseVideosApiUrl(courseId)).reply(413, { error: errorMessage });
|
||||
|
||||
const addFilesButton = screen.getAllByLabelText('file-input')[3];
|
||||
await act(async () => {
|
||||
userEvent.upload(addFilesButton, file);
|
||||
@@ -586,9 +562,9 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('404 add file should show error', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
axiosMock.onPost(getCourseVideosApiUrl(courseId)).reply(404);
|
||||
|
||||
const addFilesButton = screen.getAllByLabelText('file-input')[3];
|
||||
await act(async () => {
|
||||
userEvent.upload(addFilesButton, file);
|
||||
@@ -601,9 +577,9 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('404 add thumbnail should show error', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
axiosMock.onPost(`${getApiBaseUrl()}/video_images/${courseId}/mOckID1`).reply(404);
|
||||
|
||||
const addThumbnailButton = screen.getByTestId('video-thumbnail-mOckID1');
|
||||
const thumbnail = new File(['test'], 'sOMEUrl.jpg', { type: 'image/jpg' });
|
||||
await act(async () => {
|
||||
@@ -617,7 +593,6 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('404 upload file to server should show error', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const mockResponseData = { status: '404', ok: false, blob: () => 'Data' };
|
||||
const mockFetchResponse = Promise.reject(mockResponseData);
|
||||
@@ -637,12 +612,9 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('404 delete should show error', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible();
|
||||
|
||||
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
|
||||
expect(videoMenuButton).toBeVisible();
|
||||
|
||||
await waitFor(() => {
|
||||
axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(404);
|
||||
@@ -664,12 +636,9 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('404 usage path fetch should show error', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
expect(screen.getByTestId('grid-card-mOckID3')).toBeVisible();
|
||||
|
||||
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID3');
|
||||
expect(videoMenuButton).toBeVisible();
|
||||
|
||||
axiosMock.onGet(`${getVideosUrl(courseId)}/mOckID3/usage`).reply(404);
|
||||
await waitFor(() => {
|
||||
@@ -685,8 +654,8 @@ describe('FilesAndUploads', () => {
|
||||
});
|
||||
|
||||
it('multiple video files fetch failure should show error', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
|
||||
const selectCardButtons = screen.getAllByTestId('datatable-select-column-checkbox-cell');
|
||||
fireEvent.click(selectCardButtons[0]);
|
||||
fireEvent.click(selectCardButtons[2]);
|
||||
|
||||
@@ -21,6 +21,7 @@ const slice = createSlice({
|
||||
download: [],
|
||||
usageMetrics: [],
|
||||
transcript: [],
|
||||
loading: '',
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
@@ -76,8 +77,12 @@ const slice = createSlice({
|
||||
},
|
||||
updateErrors: (state, { payload }) => {
|
||||
const { error, message } = payload;
|
||||
const currentErrorState = state.errors[error];
|
||||
state.errors[error] = [...currentErrorState, message];
|
||||
if (error === 'loading') {
|
||||
state.errors.loading = message;
|
||||
} else {
|
||||
const currentErrorState = state.errors[error];
|
||||
state.errors[error] = [...currentErrorState, message];
|
||||
}
|
||||
},
|
||||
clearErrors: (state, { payload }) => {
|
||||
const { error } = payload;
|
||||
|
||||
@@ -55,6 +55,7 @@ export function fetchVideos(courseId) {
|
||||
if (error.response && error.response.status === 403) {
|
||||
dispatch(updateLoadingStatus({ status: RequestStatus.DENIED }));
|
||||
} else {
|
||||
dispatch(updateErrors({ error: 'loading', message: 'Failed to load videos' }));
|
||||
dispatch(updateLoadingStatus({ courseId, status: RequestStatus.FAILED }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export const initialState = {
|
||||
},
|
||||
transcriptCredentials: { cielo24: false, '3PlayMedia': false },
|
||||
},
|
||||
loadingStatus: RequestStatus.SUCCESSFUL,
|
||||
loadingStatus: RequestStatus.IN_PROGRESS,
|
||||
updatingStatus: '',
|
||||
addingStatus: '',
|
||||
deletingStatus: '',
|
||||
@@ -82,6 +82,7 @@ export const initialState = {
|
||||
download: [],
|
||||
usageMetrics: [],
|
||||
transcript: [],
|
||||
loading: '',
|
||||
},
|
||||
},
|
||||
models: {
|
||||
@@ -225,9 +226,77 @@ export const generateAddVideoApiResponse = () => ({
|
||||
],
|
||||
});
|
||||
|
||||
export const generateEmptyApiResponse = () => ([{
|
||||
export const generateEmptyApiResponse = () => ({
|
||||
previousUploads: [],
|
||||
}]);
|
||||
image_upload_url: '/video_images/course',
|
||||
video_handler_url: '/videos/course',
|
||||
encodings_download_url: '/video_encodings_download/course',
|
||||
default_video_image_url: '/static/studio/images/video-images/default_video_image.png',
|
||||
concurrent_upload_limit: 4,
|
||||
video_supported_file_formats: ['.mp4', '.mov'],
|
||||
video_upload_max_file_size: '5',
|
||||
video_image_settings: {
|
||||
video_image_upload_enabled: true,
|
||||
max_size: 2097152,
|
||||
min_size: 2048,
|
||||
max_width: 1280,
|
||||
max_height: 720,
|
||||
supported_file_formats: {
|
||||
'.bmp': 'image/bmp',
|
||||
'.bmp2': 'image/x-ms-bmp',
|
||||
'.gif': 'image/gif',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
},
|
||||
},
|
||||
is_video_transcript_enabled: false,
|
||||
active_transcript_preferences: null,
|
||||
transcript_credentials: {},
|
||||
transcript_available_languages: [{ language_code: 'ab', language_text: 'Abkhazian' }],
|
||||
video_transcript_settings: {
|
||||
transcript_download_handler_url: '/transcript_download/',
|
||||
transcript_upload_handler_url: '/transcript_upload/',
|
||||
transcript_delete_handler_url: '/transcript_delete/course',
|
||||
trancript_download_file_format: 'srt',
|
||||
transcript_preferences_handler_url: '/transcript_preferences/course',
|
||||
transcript_credentials_handler_url: '/transcript_credentials/course',
|
||||
transcription_plans: {
|
||||
Cielo24: {
|
||||
display_name: 'Cielo24',
|
||||
turnaround: { PRIORITY: 'Priority (24 hours)', STANDARD: 'Standard (48 hours)' },
|
||||
fidelity: {
|
||||
MECHANICAL: {
|
||||
display_name: 'Mechanical (75% accuracy)',
|
||||
languages: { nl: 'Dutch', en: 'English', fr: 'French' },
|
||||
},
|
||||
PREMIUM: { display_name: 'Premium (95% accuracy)', languages: { en: 'English' } },
|
||||
PROFESSIONAL: {
|
||||
display_name: 'Professional (99% accuracy)',
|
||||
languages: { ar: 'Arabic', 'zh-tw': 'Chinese - Mandarin (Traditional)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
'3PlayMedia': {
|
||||
display_name: '3Play Media',
|
||||
turnaround: {
|
||||
two_hour: '2 hours',
|
||||
same_day: 'Same day',
|
||||
rush: '24 hours (rush)',
|
||||
expedited: '2 days (expedited)',
|
||||
standard: '4 days (standard)',
|
||||
extended: '10 days (extended)',
|
||||
},
|
||||
languages: { en: 'English', el: 'Greek', zh: 'Chinese' },
|
||||
translations: {
|
||||
es: ['en'],
|
||||
en: ['el', 'en', 'zh'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pagination_context: {},
|
||||
});
|
||||
|
||||
export const generateNewVideoApiResponse = () => ({
|
||||
files: [{
|
||||
@@ -240,6 +309,8 @@ export const getStatusValue = (status) => {
|
||||
switch (status) {
|
||||
case RequestStatus.DENIED:
|
||||
return 403;
|
||||
case RequestStatus.FAILED:
|
||||
return 404;
|
||||
default:
|
||||
return 200;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user