diff --git a/Gemfile.lock b/Gemfile.lock
index f235fe683..e4a43ad6e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -18,58 +18,58 @@ GEM
specs:
action-cable-testing (0.6.1)
actioncable (>= 5.0)
- actioncable (6.0.3.5)
- actionpack (= 6.0.3.5)
+ actioncable (6.0.3.6)
+ actionpack (= 6.0.3.6)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailbox (6.0.3.5)
- actionpack (= 6.0.3.5)
- activejob (= 6.0.3.5)
- activerecord (= 6.0.3.5)
- activestorage (= 6.0.3.5)
- activesupport (= 6.0.3.5)
+ actionmailbox (6.0.3.6)
+ actionpack (= 6.0.3.6)
+ activejob (= 6.0.3.6)
+ activerecord (= 6.0.3.6)
+ activestorage (= 6.0.3.6)
+ activesupport (= 6.0.3.6)
mail (>= 2.7.1)
- actionmailer (6.0.3.5)
- actionpack (= 6.0.3.5)
- actionview (= 6.0.3.5)
- activejob (= 6.0.3.5)
+ actionmailer (6.0.3.6)
+ actionpack (= 6.0.3.6)
+ actionview (= 6.0.3.6)
+ activejob (= 6.0.3.6)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (6.0.3.5)
- actionview (= 6.0.3.5)
- activesupport (= 6.0.3.5)
+ actionpack (6.0.3.6)
+ actionview (= 6.0.3.6)
+ activesupport (= 6.0.3.6)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
- actiontext (6.0.3.5)
- actionpack (= 6.0.3.5)
- activerecord (= 6.0.3.5)
- activestorage (= 6.0.3.5)
- activesupport (= 6.0.3.5)
+ actiontext (6.0.3.6)
+ actionpack (= 6.0.3.6)
+ activerecord (= 6.0.3.6)
+ activestorage (= 6.0.3.6)
+ activesupport (= 6.0.3.6)
nokogiri (>= 1.8.5)
- actionview (6.0.3.5)
- activesupport (= 6.0.3.5)
+ actionview (6.0.3.6)
+ activesupport (= 6.0.3.6)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
- activejob (6.0.3.5)
- activesupport (= 6.0.3.5)
+ activejob (6.0.3.6)
+ activesupport (= 6.0.3.6)
globalid (>= 0.3.6)
- activemodel (6.0.3.5)
- activesupport (= 6.0.3.5)
- activerecord (6.0.3.5)
- activemodel (= 6.0.3.5)
- activesupport (= 6.0.3.5)
+ activemodel (6.0.3.6)
+ activesupport (= 6.0.3.6)
+ activerecord (6.0.3.6)
+ activemodel (= 6.0.3.6)
+ activesupport (= 6.0.3.6)
activerecord-import (1.0.7)
activerecord (>= 3.2)
- activestorage (6.0.3.5)
- actionpack (= 6.0.3.5)
- activejob (= 6.0.3.5)
- activerecord (= 6.0.3.5)
- marcel (~> 0.3.1)
- activesupport (6.0.3.5)
+ activestorage (6.0.3.6)
+ actionpack (= 6.0.3.6)
+ activejob (= 6.0.3.6)
+ activerecord (= 6.0.3.6)
+ marcel (~> 1.0.0)
+ activesupport (6.0.3.6)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@@ -261,7 +261,7 @@ GEM
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
- i18n (1.8.8)
+ i18n (1.8.9)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
inflecto (0.0.2)
@@ -303,19 +303,17 @@ GEM
nokogiri (>= 1.5.9)
mail (2.7.1)
mini_mime (>= 0.1.1)
- marcel (0.3.3)
- mimemagic (~> 0.3.2)
+ marcel (1.0.0)
maxminddb (0.1.22)
memoist (0.16.2)
method_source (1.0.0)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2020.0512)
- mimemagic (0.3.5)
mini_magick (4.10.1)
- mini_mime (1.0.2)
+ mini_mime (1.0.3)
mini_portile2 (2.5.0)
- minitest (5.14.3)
+ minitest (5.14.4)
momentjs-rails (2.20.1)
railties (>= 3.1)
msgpack (1.3.3)
@@ -325,8 +323,8 @@ GEM
net-http-persistent (4.0.0)
connection_pool (~> 2.2)
netrc (0.11.0)
- nio4r (2.5.5)
- nokogiri (1.11.1)
+ nio4r (2.5.7)
+ nokogiri (1.11.2)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
oauth (0.5.4)
@@ -357,29 +355,29 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
- rails (6.0.3.5)
- actioncable (= 6.0.3.5)
- actionmailbox (= 6.0.3.5)
- actionmailer (= 6.0.3.5)
- actionpack (= 6.0.3.5)
- actiontext (= 6.0.3.5)
- actionview (= 6.0.3.5)
- activejob (= 6.0.3.5)
- activemodel (= 6.0.3.5)
- activerecord (= 6.0.3.5)
- activestorage (= 6.0.3.5)
- activesupport (= 6.0.3.5)
+ rails (6.0.3.6)
+ actioncable (= 6.0.3.6)
+ actionmailbox (= 6.0.3.6)
+ actionmailer (= 6.0.3.6)
+ actionpack (= 6.0.3.6)
+ actiontext (= 6.0.3.6)
+ actionview (= 6.0.3.6)
+ activejob (= 6.0.3.6)
+ activemodel (= 6.0.3.6)
+ activerecord (= 6.0.3.6)
+ activestorage (= 6.0.3.6)
+ activesupport (= 6.0.3.6)
bundler (>= 1.3.0)
- railties (= 6.0.3.5)
+ railties (= 6.0.3.6)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0)
loofah (~> 2.3)
- railties (6.0.3.5)
- actionpack (= 6.0.3.5)
- activesupport (= 6.0.3.5)
+ railties (6.0.3.6)
+ actionpack (= 6.0.3.6)
+ activesupport (= 6.0.3.6)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
diff --git a/app/actions/contact_identify_action.rb b/app/actions/contact_identify_action.rb
index ff8efd3b5..91060f215 100644
--- a/app/actions/contact_identify_action.rb
+++ b/app/actions/contact_identify_action.rb
@@ -34,7 +34,9 @@ class ContactIdentifyAction
def update_contact
custom_attributes = params[:custom_attributes] ? @contact.custom_attributes.merge(params[:custom_attributes]) : @contact.custom_attributes
- @contact.update!(params.slice(:name, :email, :identifier).merge({ custom_attributes: custom_attributes }))
+ # blank identifier or email will throw unique index error
+ # TODO: replace reject { |_k, v| v.blank? } with compact_blank when rails is upgraded
+ @contact.update!(params.slice(:name, :email, :identifier).reject { |_k, v| v.blank? }.merge({ custom_attributes: custom_attributes }))
ContactAvatarJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present?
end
diff --git a/app/controllers/api/v1/widget/base_controller.rb b/app/controllers/api/v1/widget/base_controller.rb
index 52f19595e..4c7dd4220 100644
--- a/app/controllers/api/v1/widget/base_controller.rb
+++ b/app/controllers/api/v1/widget/base_controller.rb
@@ -32,7 +32,8 @@ class Api::V1::Widget::BaseController < ApplicationController
@contact_inbox = @web_widget.inbox.contact_inboxes.find_by(
source_id: auth_token_params[:source_id]
)
- @contact = @contact_inbox.contact
+ @contact = @contact_inbox&.contact
+ raise ActiveRecord::RecordNotFound unless @contact
end
def create_conversation
diff --git a/app/javascript/dashboard/components/ui/WootButton.vue b/app/javascript/dashboard/components/ui/WootButton.vue
new file mode 100644
index 000000000..e97f41f41
--- /dev/null
+++ b/app/javascript/dashboard/components/ui/WootButton.vue
@@ -0,0 +1,61 @@
+
+
+
+
diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue
index 43cf1817d..bfea8c86c 100644
--- a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue
@@ -143,7 +143,9 @@ export default {
},
parsedLastMessage() {
- return this.getPlainText(this.lastMessageInChat.content);
+ const { content_attributes: contentAttributes } = this.lastMessageInChat;
+ const { email: { subject } = {} } = contentAttributes || {};
+ return this.getPlainText(subject || this.lastMessageInChat.content);
},
chatInbox() {
diff --git a/app/javascript/dashboard/components/widgets/conversation/Message.vue b/app/javascript/dashboard/components/widgets/conversation/Message.vue
index 41d770cf6..7ad176568 100644
--- a/app/javascript/dashboard/components/widgets/conversation/Message.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/Message.vue
@@ -107,11 +107,23 @@ export default {
this.contentAttributes,
this.$t('CONVERSATION.NO_RESPONSE')
);
- let messageContent =
- this.formatMessage(this.data.content, this.isATweet) +
- botMessageContent;
- return messageContent;
+ const {
+ email: { html_content: { full: fullHTMLContent } = {} } = {},
+ } = this.contentAttributes;
+
+ if (fullHTMLContent && this.isIncoming) {
+ let parsedContent = new DOMParser().parseFromString(
+ fullHTMLContent || '',
+ 'text/html'
+ );
+ if (!parsedContent.getElementsByTagName('parsererror').length) {
+ return parsedContent.body.innerHTML;
+ }
+ }
+ return (
+ this.formatMessage(this.data.content, this.isATweet) + botMessageContent
+ );
},
contentAttributes() {
return this.data.content_attributes || {};
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/labels/AddLabelToConversation.vue b/app/javascript/dashboard/routes/dashboard/conversation/labels/AddLabelToConversation.vue
index d83bf6870..5c897b372 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/labels/AddLabelToConversation.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/labels/AddLabelToConversation.vue
@@ -87,14 +87,18 @@ export default {
},
computed: {
activeList() {
- return this.accountLabels.filter(accountLabel =>
- this.savedLabels.includes(accountLabel.title)
- );
+ return this.accountLabels
+ .filter(accountLabel => this.savedLabels.includes(accountLabel.title))
+ .sort((a, b) => {
+ return a.title.localeCompare(b.title);
+ });
},
inactiveList() {
- return this.accountLabels.filter(
- accountLabel => !this.savedLabels.includes(accountLabel.title)
- );
+ return this.accountLabels
+ .filter(accountLabel => !this.savedLabels.includes(accountLabel.title))
+ .sort((a, b) => {
+ return a.title.localeCompare(b.title);
+ });
},
},
methods: {
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index b7abc18c2..8d5a926ee 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -12,6 +12,7 @@ import hljs from 'highlight.js';
import Multiselect from 'vue-multiselect';
import WootSwitch from 'components/ui/Switch';
import WootWizard from 'components/ui/Wizard';
+import WootButton from 'components/ui/WootButton';
import { sync } from 'vuex-router-sync';
import Vuelidate from 'vuelidate';
import VTooltip from 'v-tooltip';
@@ -48,6 +49,7 @@ Vue.use(hljs.vuePlugin);
Vue.component('multiselect', Multiselect);
Vue.component('woot-switch', WootSwitch);
Vue.component('woot-wizard', WootWizard);
+Vue.component('woot-button', WootButton);
const i18nConfig = new VueI18n({
locale: 'en',
diff --git a/app/javascript/widget/components/ChatHeaderExpanded.vue b/app/javascript/widget/components/ChatHeaderExpanded.vue
index e528d336f..eaad2cbbc 100755
--- a/app/javascript/widget/components/ChatHeaderExpanded.vue
+++ b/app/javascript/widget/components/ChatHeaderExpanded.vue
@@ -49,10 +49,13 @@ export default {
diff --git a/app/javascript/widget/components/TeamAvailability.vue b/app/javascript/widget/components/TeamAvailability.vue
index 0460161c4..084538a9d 100644
--- a/app/javascript/widget/components/TeamAvailability.vue
+++ b/app/javascript/widget/components/TeamAvailability.vue
@@ -71,7 +71,7 @@ export default {
if (workingHoursEnabled) {
return this.outOfOfficeMessage;
}
- return this.$t('TEAM_AVAILABILITY.OFFLINE');
+ return '';
},
},
methods: {
diff --git a/app/javascript/widget/i18n/locale/en.json b/app/javascript/widget/i18n/locale/en.json
index 47d68c31d..33a27d18b 100644
--- a/app/javascript/widget/i18n/locale/en.json
+++ b/app/javascript/widget/i18n/locale/en.json
@@ -10,7 +10,7 @@
},
"TEAM_AVAILABILITY": {
"ONLINE": "We are online",
- "OFFLINE": "We are offline"
+ "OFFLINE": "We are away at the moment"
},
"REPLY_TIME": {
"IN_A_FEW_MINUTES": "Typically replies in a few minutes",
diff --git a/app/mailboxes/application_mailbox.rb b/app/mailboxes/application_mailbox.rb
index a13110dfe..fb3b451b0 100644
--- a/app/mailboxes/application_mailbox.rb
+++ b/app/mailboxes/application_mailbox.rb
@@ -6,7 +6,7 @@ class ApplicationMailbox < ActionMailbox::Base
def self.reply_mail?
proc do |inbound_mail_obj|
is_a_reply_email = false
- inbound_mail_obj.mail.to.each do |email|
+ inbound_mail_obj.mail.to&.each do |email|
username = email.split('@')[0]
match_result = username.match(REPLY_EMAIL_USERNAME_PATTERN)
if match_result
@@ -21,7 +21,7 @@ class ApplicationMailbox < ActionMailbox::Base
def self.support_mail?
proc do |inbound_mail_obj|
is_a_support_email = false
- inbound_mail_obj.mail.to.each do |email|
+ inbound_mail_obj.mail.to&.each do |email|
channel = Channel::Email.find_by('email = ? OR forward_to_email = ?', email, email)
if channel.present?
is_a_support_email = true
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 5d559befe..a92faf67e 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -70,7 +70,7 @@ class Notification < ApplicationRecord
I18n.t(
'notifications.notification_title.assigned_conversation_new_message',
display_id: conversation.display_id,
- content: primary_actor.content.truncate_words(10)
+ content: primary_actor.content&.truncate_words(10)
)
when 'conversation_mention'
I18n.t('notifications.notification_title.conversation_mention', display_id: conversation.display_id, name: secondary_actor.name)
diff --git a/config/app.yml b/config/app.yml
index cb7316c63..62438f95b 100644
--- a/config/app.yml
+++ b/config/app.yml
@@ -1,5 +1,5 @@
shared: &shared
- version: '1.14.2'
+ version: '1.14.3'
development:
<<: *shared
diff --git a/lib/exception_list.rb b/lib/exception_list.rb
index edb5899ab..6ea285bd0 100644
--- a/lib/exception_list.rb
+++ b/lib/exception_list.rb
@@ -1,4 +1,5 @@
module ExceptionList
URI_EXCEPTIONS = [Errno::ETIMEDOUT, Errno::ECONNREFUSED, URI::InvalidURIError, Net::OpenTimeout, SocketError].freeze
- REST_CLIENT_EXCEPTIONS = [RestClient::NotFound, RestClient::GatewayTimeout, RestClient::BadRequest, RestClient::MethodNotAllowed].freeze
+ REST_CLIENT_EXCEPTIONS = [RestClient::NotFound, RestClient::GatewayTimeout, RestClient::BadRequest,
+ RestClient::MethodNotAllowed, RestClient::Forbidden].freeze
end
diff --git a/package.json b/package.json
index 0f0639392..f41c809e3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@chatwoot/chatwoot",
- "version": "1.14.2",
+ "version": "1.14.3",
"license": "MIT",
"scripts": {
"eslint": "eslint app/javascript --fix",
diff --git a/spec/actions/contact_identify_action_spec.rb b/spec/actions/contact_identify_action_spec.rb
index dd09f7760..be139a22e 100644
--- a/spec/actions/contact_identify_action_spec.rb
+++ b/spec/actions/contact_identify_action_spec.rb
@@ -44,5 +44,15 @@ describe ::ContactIdentifyAction do
expect { contact.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
+
+ context 'when contacts with blank identifiers exist and identify action is called with blank identifier' do
+ it 'updates the attributes of contact passed in to identify action' do
+ create(:contact, account: account, identifier: '')
+ params = { identifier: '', name: 'new name' }
+ result = described_class.new(contact: contact, params: params).perform
+ expect(result.id).to eq contact.id
+ expect(result.name).to eq 'new name'
+ end
+ end
end
end
diff --git a/spec/controllers/api/v1/widget/conversations_controller_spec.rb b/spec/controllers/api/v1/widget/conversations_controller_spec.rb
index 43d60db69..9ba2d066a 100644
--- a/spec/controllers/api/v1/widget/conversations_controller_spec.rb
+++ b/spec/controllers/api/v1/widget/conversations_controller_spec.rb
@@ -25,6 +25,21 @@ RSpec.describe '/api/v1/widget/conversations/toggle_typing', type: :request do
expect(json_response['status']).to eq(conversation.status)
end
end
+
+ context 'with a conversation but invalid source id' do
+ it 'returns the correct conversation params' do
+ allow(Rails.configuration.dispatcher).to receive(:dispatch)
+
+ payload = { source_id: 'invalid source id', inbox_id: web_widget.inbox.id }
+ token = ::Widget::TokenService.new(payload: payload).generate_token
+ get '/api/v1/widget/conversations',
+ headers: { 'X-Auth-Token' => token },
+ params: { website_token: web_widget.website_token },
+ as: :json
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
end
describe 'POST /api/v1/widget/conversations' do
diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb
index 0978bab4f..be10fc411 100644
--- a/spec/models/notification_spec.rb
+++ b/spec/models/notification_spec.rb
@@ -41,6 +41,14 @@ RSpec.describe Notification do
#{message.content.truncate_words(10)}"
end
+ it 'returns appropriate title suited for the notification type assigned_conversation_new_message when attachment message' do
+ # checking content nil should be suffice for attachments
+ message = create(:message, sender: create(:user), content: nil)
+ notification = create(:notification, notification_type: 'assigned_conversation_new_message', primary_actor: message)
+
+ expect(notification.push_message_title).to eq "[New message] - ##{notification.conversation.display_id} "
+ end
+
it 'returns appropriate title suited for the notification type conversation_mention' do
message = create(:message, sender: create(:user))
notification = create(:notification, notification_type: 'conversation_mention', primary_actor: message, secondary_actor: message.sender)