Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENG-5114 integration API /userProfiles/avatar to upload the avatar #1582

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@entando/app-builder",
"version": "7.3.0",
"version": "7.3.0-SNAPSHOT",
"author": "Entando",
"homepage": "https://github.com/entando/app-builder",
"license": "MIT",
Expand Down
39 changes: 39 additions & 0 deletions src/api/avatar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { makeRequest, METHODS } from '@entando/apimanager';
import { FILE_BROWSER_FILE } from 'test/mocks/fileBrowser';

const AVATAR_ENDPOINT = '/api/userProfiles/avatar';

export const getAvatar = () =>
makeRequest({
uri: AVATAR_ENDPOINT,
method: METHODS.GET,
mockResponse: FILE_BROWSER_FILE,
useAuthentication: true,
});

export const postAvatar = avatar =>
makeRequest({
uri: AVATAR_ENDPOINT,
method: METHODS.POST,
body: avatar,
mockResponse: FILE_BROWSER_FILE,
useAuthentication: true,
});

export const updateAvatar = avatar =>
makeRequest({
uri: AVATAR_ENDPOINT,
method: METHODS.PUT,
body: avatar,
mockResponse: FILE_BROWSER_FILE,
useAuthentication: true,
});

export const deleteAvatar = () =>
makeRequest({
uri: AVATAR_ENDPOINT,
method: METHODS.DELETE,
mockResponse: FILE_BROWSER_FILE,
useAuthentication: true,
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a new line at the end of the file


27 changes: 27 additions & 0 deletions src/state/avatar/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { postAvatar } from 'api/avatar';
import { getBase64 } from 'state/file-browser/actions';
import { toggleLoading } from 'state/loading/actions';
import { addToast, addErrors, TOAST_SUCCESS, TOAST_ERROR } from '@entando/messages';

export const createFileObject = async (avatar) => {
const base64 = await getBase64(avatar);
return { fileName: avatar.name, base64 };
};

export const uploadAvatar =
(avatar, loader = 'uploadAvatar') =>
async (dispatch) => {
try {
dispatch(toggleLoading(loader));
const requestObject = await createFileObject(avatar);
await postAvatar(requestObject);
dispatch(toggleLoading(loader));
dispatch(addToast({ id: 'fileBrowser.uploadFileComplete' }, TOAST_SUCCESS));
} catch (error) {
dispatch(toggleLoading(loader));
const message = { id: 'fileBrowser.uploadFileError', values: { errmsg: error } };
dispatch(addErrors(error));
dispatch(addToast(message, TOAST_ERROR));
}
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a new line at the end of the file


2 changes: 1 addition & 1 deletion src/state/file-browser/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const wrapApiCall = apiFunc => (...args) => async (dispatch) => {

// thunks

const getBase64 = file => (
export const getBase64 = file => (
new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file);
Expand Down
4 changes: 2 additions & 2 deletions src/ui/users/common/ProfileImageUploader.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Dropdown, MenuItem } from 'patternfly-react';
import { useDispatch } from 'react-redux';
import md5 from 'md5';

import { uploadFile } from 'state/file-browser/actions';
import { useDynamicResourceUrl } from 'hooks/useDynamicResourceUrl';
import { uploadAvatar } from 'state/avatar/actions';

const FILE_BROWSER_PATH = 'static/profile';
const GRAVATAR = 'GRAVATAR';
Expand All @@ -24,7 +24,7 @@ const ProfileImageUploader = ({
const imageProvider = useDynamicResourceUrl(FILE_BROWSER_PATH);

const onFileChange = ({ target: { files } }) => {
dispatch(uploadFile(files[0], FILE_BROWSER_PATH)).then(() => onChange(files[0].name));
dispatch(uploadAvatar(files[0])).then(() => onChange(files[0].name));
};

const handleUploadClick = () => {
Expand Down
44 changes: 44 additions & 0 deletions test/state/avatar/actions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { createFileObject, uploadAvatar } from 'state/avatar/actions';
import * as actionsHelper from 'state/avatar/actions';
import * as apiHelper from 'api/avatar';

describe('createFileObject', () => {
it('should create a file object', async () => {
const avatar = new File(['test data'], 'test.png', { type: 'image/png' });
const result = await createFileObject(avatar);
expect(result).toEqual({ fileName: 'test.png', base64: result.base64 });
});
});

describe('uploadAvatar', () => {
it('should upload avatar successfully', async () => {
const avatar = new File(['test data'], 'test.png', { type: 'image/png' });
const dispatch = jest.fn();
jest.spyOn(actionsHelper, 'createFileObject').mockResolvedValue({ fileName: 'test.png', base64: 'base64encodedstring' });
jest.spyOn(apiHelper, 'postAvatar').mockResolvedValue();

await uploadAvatar(avatar)(dispatch);

expect(dispatch).toHaveBeenCalledWith({ payload: { id: 'uploadAvatar' }, type: 'loading/toggle-loading' });
expect(dispatch).toHaveBeenCalledWith({ payload: { id: 'uploadAvatar' }, type: 'loading/toggle-loading' });
expect(dispatch).toHaveBeenCalledWith({ payload: { message: { id: 'fileBrowser.uploadFileComplete' }, type: 'success' }, type: 'toasts/add-toast' });
expect(dispatch).toHaveBeenCalledTimes(3);
});

it('should handle avatar upload error', async () => {
const avatar = { name: 'test.jpg' };
const dispatch = jest.fn();
const error = new Error('Upload failed');
jest.spyOn(actionsHelper, 'createFileObject').mockResolvedValue({ fileName: avatar.name, base64: 'base64encodedstring' });
jest.spyOn(apiHelper, 'postAvatar').mockRejectedValue(error);

await uploadAvatar(avatar)(dispatch);

expect(dispatch).toHaveBeenCalledWith({ payload: { id: 'uploadAvatar' }, type: 'loading/toggle-loading' });
expect(dispatch).toHaveBeenCalledWith({ payload: { id: 'uploadAvatar' }, type: 'loading/toggle-loading' });
expect(dispatch).toHaveBeenCalledWith({ payload: { message: { id: 'fileBrowser.uploadFileError', values: { errmsg: TypeError('[TypeError: Failed to execute \'readAsDataURL\' on \'FileReader\': parameter 1 is not of type \'Blob\'.]') } }, type: 'error' }, type: 'toasts/add-toast' });
expect(dispatch).toHaveBeenCalledWith({ type: 'errors/add-errors', payload: { errors: TypeError('[TypeError: Failed to execute \'readAsDataURL\' on \'FileReader\': parameter 1 is not of type \'Blob\'.]') } });
expect(dispatch).toHaveBeenCalledTimes(4);
});
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a new line at the end of the file


Loading