Skip to content

Commit

Permalink
Media Upload Queue (#6541)
Browse files Browse the repository at this point in the history
  • Loading branch information
swissspidy committed Mar 9, 2021
1 parent dd61354 commit f46a2e8
Show file tree
Hide file tree
Showing 44 changed files with 1,799 additions and 649 deletions.
12 changes: 10 additions & 2 deletions assets/src/edit-story/app/media/local/actions.js
Expand Up @@ -37,10 +37,11 @@ export const fetchMediaError = (dispatch) => (properties) =>
});
export const setNextPage = (dispatch) => (properties) =>
common.setNextPage(dispatch)({ ...properties, provider: 'local' });
export const updateMediaElement = (dispatch) => (properties) =>
export const updateMediaElement = (dispatch) => ({ id, data }) =>
common.updateMediaElement(dispatch)({
...properties,
provider: 'local',
id,
data,
});
export const deleteMediaElement = (dispatch) => (properties) =>
common.deleteMediaElement(dispatch)({
Expand Down Expand Up @@ -77,3 +78,10 @@ export const setMedia = (dispatch) => ({ media }) => {
payload: { media },
});
};

export const prependMedia = (dispatch) => ({ media }) => {
dispatch({
type: types.LOCAL_MEDIA_PREPEND_MEDIA,
payload: { media },
});
};
9 changes: 9 additions & 0 deletions assets/src/edit-story/app/media/local/reducer.js
Expand Up @@ -110,6 +110,15 @@ function reducer(state = INITIAL_STATE, { type, payload }) {
};
}

case types.LOCAL_MEDIA_PREPEND_MEDIA: {
const { media } = payload;

return {
...state,
media: [...media, ...state.media],
};
}

case types.LOCAL_MEDIA_ADD_PROCESSING: {
const { id } = payload;
if (!id || state.processing.includes(id)) {
Expand Down
11 changes: 6 additions & 5 deletions assets/src/edit-story/app/media/local/typedefs.js
Expand Up @@ -29,11 +29,12 @@
/**
* @typedef UpdateMediaElementPayload
* @property {string} id id of the media to update
* @property {string} posterId poster id
* @property {string} poster poster
* @property {number} height height in pixels
* @property {number} width width in pixels
* @property {string} alt alt string that describes the media
* @property {Object} data Updated data.
* @property {string} data.posterId poster id
* @property {string} data.poster poster
* @property {number} data.height height in pixels
* @property {number} data.width width in pixels
* @property {string} data.alt alt string that describes the media
*/

/**
Expand Down
1 change: 1 addition & 0 deletions assets/src/edit-story/app/media/local/types.js
Expand Up @@ -18,5 +18,6 @@ export const LOCAL_MEDIA_RESET_FILTERS = 'LOCAL_MEDIA_RESET_FILTERS';
export const LOCAL_MEDIA_SET_SEARCH_TERM = 'LOCAL_MEDIA_SET_SEARCH_TERM';
export const LOCAL_MEDIA_SET_MEDIA_TYPE = 'LOCAL_MEDIA_SET_MEDIA_TYPE';
export const LOCAL_MEDIA_SET_MEDIA = 'LOCAL_MEDIA_SET_MEDIA';
export const LOCAL_MEDIA_PREPEND_MEDIA = 'LOCAL_MEDIA_PREPEND_MEDIA';
export const LOCAL_MEDIA_REMOVE_PROCESSING = 'LOCAL_MEDIA_REMOVE_PROCESSING';
export const LOCAL_MEDIA_ADD_PROCESSING = 'LOCAL_MEDIA_ADD_PROCESSING';
Expand Up @@ -51,7 +51,7 @@ export default function useContextValueProvider(reducerState, reducerActions) {
fetchMediaSuccess,
fetchMediaError,
resetFilters,
setMedia,
prependMedia,
setMediaType,
setSearchTerm,
setNextPage,
Expand Down Expand Up @@ -107,7 +107,9 @@ export default function useContextValueProvider(reducerState, reducerActions) {

const { uploadMedia, isUploading, isTranscoding } = useUploadMedia({
media,
setMedia,
prependMedia,
updateMediaElement,
deleteMediaElement,
});
const { uploadVideoFrame } = useUploadVideoFrame({
updateMediaElement,
Expand Down
Expand Up @@ -150,6 +150,7 @@ describe('useMedia3pApi', () => {
length: undefined,
lengthFormatted: undefined,
local: false,
isPlaceholder: false,
mimeType: 'image/jpeg',
output: undefined,
poster: undefined,
Expand Down
8 changes: 2 additions & 6 deletions assets/src/edit-story/app/media/pagination/actions.js
Expand Up @@ -62,17 +62,13 @@ export const setNextPage = (dispatch) => ({ provider }) => {
});
};

export const updateMediaElement = (dispatch) => ({
provider,
id,
...properties
}) => {
export const updateMediaElement = (dispatch) => ({ provider, id, data }) => {
dispatch({
type: types.UPDATE_MEDIA_ELEMENT,
payload: {
provider,
id,
...properties,
data,
},
});
};
Expand Down
5 changes: 3 additions & 2 deletions assets/src/edit-story/app/media/pagination/reducer.js
Expand Up @@ -103,7 +103,7 @@ function reducer(state = INITIAL_STATE, { type, payload }) {
}

case types.UPDATE_MEDIA_ELEMENT: {
const { id, ...properties } = payload;
const { id, provider, data } = payload;

const mediaIndex = state.media.findIndex((media) => media.id === id);
if (mediaIndex === -1) {
Expand All @@ -112,7 +112,8 @@ function reducer(state = INITIAL_STATE, { type, payload }) {

const updatedMediaElement = {
...state.media[mediaIndex],
...properties,
provider,
...data,
};

const newMedia = [
Expand Down
135 changes: 37 additions & 98 deletions assets/src/edit-story/app/media/test/useUploadMedia.js
Expand Up @@ -28,15 +28,33 @@ import {
} from '../../../app/media/utils';
import useUploadMedia from '../useUploadMedia';

const mockUploadFile = jest.fn();
const mockValidateFileForUpload = jest.fn();

jest.mock('../../uploader', () => ({
useUploader: jest.fn(() => ({
actions: {
uploadFile: mockUploadFile,
validateFileForUpload: mockValidateFileForUpload,
},
})),
}));

const mockAddItem = jest.fn();
const mockRemoveItem = jest.fn();

jest.mock('../utils/useMediaUploadQueue', () => ({
__esModule: true,
default: jest.fn(() => ({
actions: {
addItem: mockAddItem,
removeItem: mockRemoveItem,
},
state: {
isUploading: false,
isTranscoding: false,
pending: [],
progress: [],
processed: [],
failures: [],
},
})),
}));
Expand All @@ -60,6 +78,17 @@ jest.mock('../../config', () => ({

jest.mock('../../../app/media/utils');

jest.mock('../utils/useTranscodeVideo', () => ({
__esModule: true,
default: jest.fn(() => ({
isFeatureEnabled: true,
isTranscodingEnabled: true,
canTranscodeFile: jest.fn(),
isFileTooLarge: jest.fn(),
transcodeVideo: jest.fn(),
})),
}));

function setup() {
const media = [
{ type: 'image/jpeg', src: 'image1.jpg' },
Expand Down Expand Up @@ -95,7 +124,6 @@ function isLocalResourceSupported(resource) {
describe('useUploadMedia', () => {
beforeEach(() => {
mockShowSnackbar.mockReset();
mockUploadFile.mockReset();
getResourceFromLocalFile.mockReset();
getResourceFromAttachment.mockReset();

Expand All @@ -108,33 +136,18 @@ describe('useUploadMedia', () => {
getResourceFromAttachment.mockImplementation((file) => file);
});

it('should bail early if no files are passed', async () => {
const { uploadMedia, setMedia } = setup();
it('bails early if no files are passed', async () => {
const { uploadMedia } = setup();
await act(() => uploadMedia([]));

expect(setMedia).toHaveBeenCalledTimes(0);
expect(mockAddItem).not.toHaveBeenCalled();
});

it('should display an error message if local resource could not be created', async () => {
getResourceFromLocalFile.mockImplementation(() => {
it('displays error message if file does not validate for upload', async () => {
mockValidateFileForUpload.mockImplementationOnce(() => {
throw new Error('Whoopsie');
});
const { uploadMedia, setMedia } = setup();

const newFiles = [{ type: 'video/mpeg', src: 'video.mpg' }];
await act(() => uploadMedia(newFiles));

expect(setMedia).toHaveBeenCalledTimes(0);
expect(mockShowSnackbar).toHaveBeenCalledTimes(1);
expect(mockShowSnackbar).toHaveBeenCalledWith({
message: 'Whoopsie',
});
});

it('should display a fallback error message if local resource could not be created', async () => {
getResourceFromLocalFile.mockImplementation(() => {
throw new Error();
});
const { uploadMedia, setMedia } = setup();

const newFiles = [{ type: 'video/mpeg', src: 'video.mpg' }];
Expand All @@ -143,81 +156,7 @@ describe('useUploadMedia', () => {
expect(setMedia).toHaveBeenCalledTimes(0);
expect(mockShowSnackbar).toHaveBeenCalledTimes(1);
expect(mockShowSnackbar).toHaveBeenCalledWith({
message: 'File could not be uploaded. Please try a different file.',
});
});

it('should display an error message when uploading files with no type', async () => {
const { uploadMedia, setMedia } = setup();

const newFiles = [{ type: '', src: 'video1.mkv' }];
await act(() => uploadMedia(newFiles));

expect(setMedia).toHaveBeenCalledTimes(0);
expect(mockShowSnackbar).toHaveBeenCalledTimes(1);
expect(mockShowSnackbar).toHaveBeenCalledWith({
message:
'One or more files could not be uploaded. Please try a different file.',
});
});

it('should only setMedia for files supported by getResourceFromLocalFile', async () => {
const { uploadMedia, setMedia, media } = setup();

const newFiles = [
{ type: 'video/mp4', src: 'video1.mp4' },
{ type: 'text/plain', src: 'text.txt' }, // Unsupported File Format
{ type: 'image/jpeg', src: 'image5.jpg' },
];
await act(() => uploadMedia(newFiles));

// Should have skipped each unsupported file.
expect(setMedia).toHaveBeenCalledTimes(2);
expect(setMedia.mock.calls[0][0].media).toIncludeSameMembers([
...newFiles.filter(isLocalResourceSupported),
...media,
]);
});

it('before upload should setMedia for local version of resource', async () => {
const { uploadMedia, setMedia, media } = setup();

const newFiles = [
{ type: 'video/mp4', src: 'video1.mp4' },
{ type: 'image/jpeg', src: 'image5.jpg' },
];
await act(() => uploadMedia(newFiles));

// Should have added each local file to be uploaded.
expect(setMedia).toHaveBeenCalledTimes(2);
expect(setMedia.mock.calls[0][0].media).toIncludeSameMembers([
...newFiles,
...media,
]);
});

it('after upload should setMedia for remote version of resource', async () => {
const { uploadMedia, setMedia, media } = setup();

mockUploadFile.mockImplementation((file) => {
return {
...file,
title: `title of ${file.src}`,
};
message: 'Whoopsie',
});

const newFiles = [
{ type: 'image/jpeg', src: 'image5.jpg' },
{ type: 'video/mp4', src: 'video1.mp4' },
];
await act(() => uploadMedia(newFiles));

// Should have updated each uploaded file with the remote version.
expect(setMedia).toHaveBeenCalledTimes(2);
expect(setMedia.mock.calls[1][0].media).toIncludeSameMembers([
{ type: 'image/jpeg', src: 'image5.jpg', title: 'title of image5.jpg' },
{ type: 'video/mp4', src: 'video1.mp4', title: 'title of video1.mp4' },
...media,
]);
});
});

0 comments on commit f46a2e8

Please sign in to comment.