feat(v4): Compose new conversation without multiple clicks (#10545)

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Pranav <pranavrajs@gmail.com>
This commit is contained in:
Sivin Varghese
2024-12-06 09:46:29 +05:30
committed by GitHub
parent 67c90231b6
commit b116ab5ad3
26 changed files with 850 additions and 101 deletions

View File

@@ -0,0 +1,146 @@
import { useFileUpload } from '../useFileUpload';
import { useMapGetter } from 'dashboard/composables/store';
import { useAlert } from 'dashboard/composables';
import { useI18n } from 'vue-i18n';
import { DirectUpload } from 'activestorage';
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
import { MAXIMUM_FILE_UPLOAD_SIZE_TWILIO_SMS_CHANNEL } from 'shared/constants/messages';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables', () => ({
useAlert: vi.fn(message => message),
}));
vi.mock('vue-i18n');
vi.mock('activestorage');
vi.mock('shared/helpers/FileHelper');
describe('useFileUpload', () => {
const mockAttachFile = vi.fn();
const mockTranslate = vi.fn();
const mockFile = {
file: new File(['test'], 'test.jpg', { type: 'image/jpeg' }),
};
beforeEach(() => {
vi.clearAllMocks();
useMapGetter.mockImplementation(getter => {
const getterMap = {
getCurrentAccountId: { value: '123' },
getCurrentUser: { value: { access_token: 'test-token' } },
getSelectedChat: { value: { id: '456' } },
'globalConfig/get': { value: { directUploadsEnabled: true } },
};
return getterMap[getter];
});
useI18n.mockReturnValue({ t: mockTranslate });
checkFileSizeLimit.mockReturnValue(true);
});
it('should handle direct file upload when enabled', () => {
const { onFileUpload } = useFileUpload({
isATwilioSMSChannel: false,
attachFile: mockAttachFile,
});
const mockBlob = { signed_id: 'test-blob' };
DirectUpload.mockImplementation(() => ({
create: callback => callback(null, mockBlob),
}));
onFileUpload(mockFile);
expect(DirectUpload).toHaveBeenCalledWith(
mockFile.file,
'/api/v1/accounts/123/conversations/456/direct_uploads',
expect.any(Object)
);
expect(mockAttachFile).toHaveBeenCalledWith({
file: mockFile,
blob: mockBlob,
});
});
it('should handle indirect file upload when direct upload is disabled', () => {
useMapGetter.mockImplementation(getter => {
const getterMap = {
getCurrentAccountId: { value: '123' },
getCurrentUser: { value: { access_token: 'test-token' } },
getSelectedChat: { value: { id: '456' } },
'globalConfig/get': { value: { directUploadsEnabled: false } },
};
return getterMap[getter];
});
const { onFileUpload } = useFileUpload({
isATwilioSMSChannel: false,
attachFile: mockAttachFile,
});
onFileUpload(mockFile);
expect(DirectUpload).not.toHaveBeenCalled();
expect(mockAttachFile).toHaveBeenCalledWith({ file: mockFile });
});
it('should show alert when file size exceeds limit', () => {
checkFileSizeLimit.mockReturnValue(false);
mockTranslate.mockReturnValue('File size exceeds limit');
const { onFileUpload } = useFileUpload({
isATwilioSMSChannel: false,
attachFile: mockAttachFile,
});
onFileUpload(mockFile);
expect(useAlert).toHaveBeenCalledWith('File size exceeds limit');
expect(mockAttachFile).not.toHaveBeenCalled();
});
it('should use different max file size for Twilio SMS channel', () => {
const { onFileUpload } = useFileUpload({
isATwilioSMSChannel: true,
attachFile: mockAttachFile,
});
onFileUpload(mockFile);
expect(checkFileSizeLimit).toHaveBeenCalledWith(
mockFile,
MAXIMUM_FILE_UPLOAD_SIZE_TWILIO_SMS_CHANNEL
);
});
it('should handle direct upload errors', () => {
const mockError = 'Upload failed';
DirectUpload.mockImplementation(() => ({
create: callback => callback(mockError, null),
}));
const { onFileUpload } = useFileUpload({
isATwilioSMSChannel: false,
attachFile: mockAttachFile,
});
onFileUpload(mockFile);
expect(useAlert).toHaveBeenCalledWith(mockError);
expect(mockAttachFile).not.toHaveBeenCalled();
});
it('should do nothing when file is null', () => {
const { onFileUpload } = useFileUpload({
isATwilioSMSChannel: false,
attachFile: mockAttachFile,
});
onFileUpload(null);
expect(checkFileSizeLimit).not.toHaveBeenCalled();
expect(mockAttachFile).not.toHaveBeenCalled();
expect(useAlert).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,91 @@
import { computed } from 'vue';
import { useMapGetter } from 'dashboard/composables/store';
import { useAlert } from 'dashboard/composables';
import { useI18n } from 'vue-i18n';
import { DirectUpload } from 'activestorage';
import {
MAXIMUM_FILE_UPLOAD_SIZE,
MAXIMUM_FILE_UPLOAD_SIZE_TWILIO_SMS_CHANNEL,
} from 'shared/constants/messages';
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
/**
* Composable for handling file uploads in conversations
* @param {Object} options - Configuration options
* @param {boolean} options.isATwilioSMSChannel - Whether the current channel is Twilio SMS
* @param {Function} options.attachFile - Callback function to handle file attachment
* @returns {Object} File upload methods and utilities
*/
export const useFileUpload = ({ isATwilioSMSChannel, attachFile }) => {
const { t } = useI18n();
const accountId = useMapGetter('getCurrentAccountId');
const currentUser = useMapGetter('getCurrentUser');
const currentChat = useMapGetter('getSelectedChat');
const globalConfig = useMapGetter('globalConfig/get');
const maxFileSize = computed(() =>
isATwilioSMSChannel
? MAXIMUM_FILE_UPLOAD_SIZE_TWILIO_SMS_CHANNEL
: MAXIMUM_FILE_UPLOAD_SIZE
);
const handleDirectFileUpload = file => {
if (!file) return;
if (checkFileSizeLimit(file, maxFileSize.value)) {
const upload = new DirectUpload(
file.file,
`/api/v1/accounts/${accountId.value}/conversations/${currentChat.value.id}/direct_uploads`,
{
directUploadWillCreateBlobWithXHR: xhr => {
xhr.setRequestHeader(
'api_access_token',
currentUser.value.access_token
);
},
}
);
upload.create((error, blob) => {
if (error) {
useAlert(error);
} else {
attachFile({ file, blob });
}
});
} else {
useAlert(
t('CONVERSATION.FILE_SIZE_LIMIT', {
MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE: maxFileSize.value,
})
);
}
};
const handleIndirectFileUpload = file => {
if (!file) return;
if (checkFileSizeLimit(file, maxFileSize.value)) {
attachFile({ file });
} else {
useAlert(
t('CONVERSATION.FILE_SIZE_LIMIT', {
MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE: maxFileSize.value,
})
);
}
};
const onFileUpload = file => {
if (globalConfig.value.directUploadsEnabled) {
handleDirectFileUpload(file);
} else {
handleIndirectFileUpload(file);
}
};
return {
onFileUpload,
};
};