From 7a3303e8412a2d5ad9760b84022c7f91a653e96b Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Wed, 20 Nov 2024 06:24:01 +0800 Subject: [PATCH 01/74] fix: Undefined method `encode' for nil for avatar from url job (#10450) Invalid urls supplied to the job was causing sentry issues. The issue primarily occurs when the download file.original_filename comes out as empty fixes: https://github.com/chatwoot/chatwoot/issues/10449 --- app/jobs/avatar/avatar_from_url_job.rb | 15 ++++++++++++++- spec/jobs/avatar/avatar_from_url_job_spec.rb | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/app/jobs/avatar/avatar_from_url_job.rb b/app/jobs/avatar/avatar_from_url_job.rb index f1efea225..f0d877b54 100644 --- a/app/jobs/avatar/avatar_from_url_job.rb +++ b/app/jobs/avatar/avatar_from_url_job.rb @@ -8,8 +8,21 @@ class Avatar::AvatarFromUrlJob < ApplicationJob avatar_url, max_size: 15 * 1024 * 1024 ) - avatarable.avatar.attach(io: avatar_file, filename: avatar_file.original_filename, content_type: avatar_file.content_type) + if valid_image?(avatar_file) + avatarable.avatar.attach(io: avatar_file, filename: avatar_file.original_filename, + content_type: avatar_file.content_type) + end rescue Down::NotFound, Down::Error => e Rails.logger.error "Exception: invalid avatar url #{avatar_url} : #{e.message}" end + + private + + def valid_image?(file) + return false if file.original_filename.blank? + + # TODO: check if the file is an actual image + + true + end end diff --git a/spec/jobs/avatar/avatar_from_url_job_spec.rb b/spec/jobs/avatar/avatar_from_url_job_spec.rb index b9886316a..25276bc67 100644 --- a/spec/jobs/avatar/avatar_from_url_job_spec.rb +++ b/spec/jobs/avatar/avatar_from_url_job_spec.rb @@ -17,4 +17,20 @@ RSpec.describe Avatar::AvatarFromUrlJob do described_class.perform_now(avatarable, avatar_url) expect(avatarable.avatar).to be_attached end + + # ref: https://github.com/chatwoot/chatwoot/issues/10449 + it 'will not throw error if the avatar url is not valid and the file does not have a filename' do + # Create a temporary file with no filename and content type application/xml + temp_file = Tempfile.new(['invalid', '.xml']) + temp_file.write('content') + temp_file.rewind + + expect(Down).to receive(:download).with(avatar_url, max_size: 15 * 1024 * 1024) + .and_return(ActionDispatch::Http::UploadedFile.new(tempfile: temp_file, type: 'application/xml')) + + expect { described_class.perform_now(avatarable, avatar_url) }.not_to raise_error + + temp_file.close + temp_file.unlink # deletes the temp file + end end From a7e3d443c982fa45b675c623888b63e7978eab1f Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Wed, 20 Nov 2024 03:55:45 +0530 Subject: [PATCH 02/74] feat: Add the new select menu component (#10445) --- .../selectmenu/SelectMenu.story.vue | 46 ++++++++++++ .../components-next/selectmenu/SelectMenu.vue | 71 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 app/javascript/dashboard/components-next/selectmenu/SelectMenu.story.vue create mode 100644 app/javascript/dashboard/components-next/selectmenu/SelectMenu.vue diff --git a/app/javascript/dashboard/components-next/selectmenu/SelectMenu.story.vue b/app/javascript/dashboard/components-next/selectmenu/SelectMenu.story.vue new file mode 100644 index 000000000..13d823889 --- /dev/null +++ b/app/javascript/dashboard/components-next/selectmenu/SelectMenu.story.vue @@ -0,0 +1,46 @@ + + + diff --git a/app/javascript/dashboard/components-next/selectmenu/SelectMenu.vue b/app/javascript/dashboard/components-next/selectmenu/SelectMenu.vue new file mode 100644 index 000000000..cc584dcbb --- /dev/null +++ b/app/javascript/dashboard/components-next/selectmenu/SelectMenu.vue @@ -0,0 +1,71 @@ + + + From 759615d041c34a2e42781de136526c6526a5d75f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 20 Nov 2024 03:59:45 +0530 Subject: [PATCH 03/74] fix: Update the dropdown bg to match the design system (#10438) This PR updates the background used in dropdown to match our design system. Previous PR failed to add this correctly. --------- Co-authored-by: Pranav --- .../components-next/dropdown-menu/base/DropdownBody.vue | 2 +- .../components-next/dropdown-menu/base/DropdownItem.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/dashboard/components-next/dropdown-menu/base/DropdownBody.vue b/app/javascript/dashboard/components-next/dropdown-menu/base/DropdownBody.vue index 4fde89306..7616c16d8 100644 --- a/app/javascript/dashboard/components-next/dropdown-menu/base/DropdownBody.vue +++ b/app/javascript/dashboard/components-next/dropdown-menu/base/DropdownBody.vue @@ -1,7 +1,7 @@ diff --git a/app/javascript/dashboard/components-next/combobox/ComboBox.vue b/app/javascript/dashboard/components-next/combobox/ComboBox.vue index 7fce6f64c..de84974e6 100644 --- a/app/javascript/dashboard/components-next/combobox/ComboBox.vue +++ b/app/javascript/dashboard/components-next/combobox/ComboBox.vue @@ -43,7 +43,7 @@ const props = defineProps({ }, }); -const emit = defineEmits(['update:modelValue']); +const emit = defineEmits(['update:modelValue', 'search']); const { t } = useI18n(); @@ -118,13 +118,13 @@ watch( diff --git a/app/javascript/dashboard/components-next/combobox/ComboBoxDropdown.vue b/app/javascript/dashboard/components-next/combobox/ComboBoxDropdown.vue index 4a0e3856c..a39657624 100644 --- a/app/javascript/dashboard/components-next/combobox/ComboBoxDropdown.vue +++ b/app/javascript/dashboard/components-next/combobox/ComboBoxDropdown.vue @@ -11,10 +11,6 @@ const props = defineProps({ type: Array, required: true, }, - searchValue: { - type: String, - required: true, - }, searchPlaceholder: { type: String, default: '', @@ -33,10 +29,15 @@ const props = defineProps({ }, }); -const emit = defineEmits(['update:searchValue', 'select']); +const emit = defineEmits(['update:searchValue', 'select', 'search']); const { t } = useI18n(); +const searchValue = defineModel('searchValue', { + type: String, + default: '', +}); + const searchInput = ref(null); const isSelected = option => { @@ -46,6 +47,11 @@ const isSelected = option => { return option.value === props.selectedValues; }; +const onInputSearch = event => { + searchValue.value = event.target.value; + emit('search', event.target.value); +}; + defineExpose({ focus: () => searchInput.value?.focus(), }); @@ -64,7 +70,7 @@ defineExpose({ type="search" :placeholder="searchPlaceholder || t('COMBOBOX.SEARCH_PLACEHOLDER')" class="w-full py-2 pl-10 pr-2 text-sm border-none rounded-t-md bg-n-solid-1 text-slate-900 dark:text-slate-50" - @input="emit('update:searchValue', $event.target.value)" + @input="onInputSearch" />
    { alertDialog.value.open(); @@ -17,6 +18,9 @@ const openEditDialog = () => { const openConfirmDialog = () => { confirmDialog.value.open(); }; +const openConfirmDialogWithCustomFooter = () => { + confirmDialogWithCustomFooter.value.open(); +}; // eslint-disable-next-line no-unused-vars const onConfirm = dialog => {}; @@ -44,24 +48,22 @@ const onConfirm = dialog => {}; confirm-button-label="Save" @confirm="onConfirm()" > - +
    + + +
    @@ -77,5 +79,21 @@ const onConfirm = dialog => {}; @confirm="onConfirm()" /> + + +