Merge branch 'release/1.14.3'

This commit is contained in:
Sojan
2021-03-27 18:45:38 +05:30
19 changed files with 201 additions and 82 deletions

View File

@@ -18,58 +18,58 @@ GEM
specs: specs:
action-cable-testing (0.6.1) action-cable-testing (0.6.1)
actioncable (>= 5.0) actioncable (>= 5.0)
actioncable (6.0.3.5) actioncable (6.0.3.6)
actionpack (= 6.0.3.5) actionpack (= 6.0.3.6)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (6.0.3.5) actionmailbox (6.0.3.6)
actionpack (= 6.0.3.5) actionpack (= 6.0.3.6)
activejob (= 6.0.3.5) activejob (= 6.0.3.6)
activerecord (= 6.0.3.5) activerecord (= 6.0.3.6)
activestorage (= 6.0.3.5) activestorage (= 6.0.3.6)
activesupport (= 6.0.3.5) activesupport (= 6.0.3.6)
mail (>= 2.7.1) mail (>= 2.7.1)
actionmailer (6.0.3.5) actionmailer (6.0.3.6)
actionpack (= 6.0.3.5) actionpack (= 6.0.3.6)
actionview (= 6.0.3.5) actionview (= 6.0.3.6)
activejob (= 6.0.3.5) activejob (= 6.0.3.6)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.0.3.5) actionpack (6.0.3.6)
actionview (= 6.0.3.5) actionview (= 6.0.3.6)
activesupport (= 6.0.3.5) activesupport (= 6.0.3.6)
rack (~> 2.0, >= 2.0.8) rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.3.5) actiontext (6.0.3.6)
actionpack (= 6.0.3.5) actionpack (= 6.0.3.6)
activerecord (= 6.0.3.5) activerecord (= 6.0.3.6)
activestorage (= 6.0.3.5) activestorage (= 6.0.3.6)
activesupport (= 6.0.3.5) activesupport (= 6.0.3.6)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (6.0.3.5) actionview (6.0.3.6)
activesupport (= 6.0.3.5) activesupport (= 6.0.3.6)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.0.3.5) activejob (6.0.3.6)
activesupport (= 6.0.3.5) activesupport (= 6.0.3.6)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.0.3.5) activemodel (6.0.3.6)
activesupport (= 6.0.3.5) activesupport (= 6.0.3.6)
activerecord (6.0.3.5) activerecord (6.0.3.6)
activemodel (= 6.0.3.5) activemodel (= 6.0.3.6)
activesupport (= 6.0.3.5) activesupport (= 6.0.3.6)
activerecord-import (1.0.7) activerecord-import (1.0.7)
activerecord (>= 3.2) activerecord (>= 3.2)
activestorage (6.0.3.5) activestorage (6.0.3.6)
actionpack (= 6.0.3.5) actionpack (= 6.0.3.6)
activejob (= 6.0.3.5) activejob (= 6.0.3.6)
activerecord (= 6.0.3.5) activerecord (= 6.0.3.6)
marcel (~> 0.3.1) marcel (~> 1.0.0)
activesupport (6.0.3.5) activesupport (6.0.3.6)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
@@ -261,7 +261,7 @@ GEM
mime-types (~> 3.0) mime-types (~> 3.0)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpclient (2.8.3) httpclient (2.8.3)
i18n (1.8.8) i18n (1.8.9)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
ice_nine (0.11.2) ice_nine (0.11.2)
inflecto (0.0.2) inflecto (0.0.2)
@@ -303,19 +303,17 @@ GEM
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.7.1) mail (2.7.1)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
marcel (0.3.3) marcel (1.0.0)
mimemagic (~> 0.3.2)
maxminddb (0.1.22) maxminddb (0.1.22)
memoist (0.16.2) memoist (0.16.2)
method_source (1.0.0) method_source (1.0.0)
mime-types (3.3.1) mime-types (3.3.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2020.0512) mime-types-data (3.2020.0512)
mimemagic (0.3.5)
mini_magick (4.10.1) mini_magick (4.10.1)
mini_mime (1.0.2) mini_mime (1.0.3)
mini_portile2 (2.5.0) mini_portile2 (2.5.0)
minitest (5.14.3) minitest (5.14.4)
momentjs-rails (2.20.1) momentjs-rails (2.20.1)
railties (>= 3.1) railties (>= 3.1)
msgpack (1.3.3) msgpack (1.3.3)
@@ -325,8 +323,8 @@ GEM
net-http-persistent (4.0.0) net-http-persistent (4.0.0)
connection_pool (~> 2.2) connection_pool (~> 2.2)
netrc (0.11.0) netrc (0.11.0)
nio4r (2.5.5) nio4r (2.5.7)
nokogiri (1.11.1) nokogiri (1.11.2)
mini_portile2 (~> 2.5.0) mini_portile2 (~> 2.5.0)
racc (~> 1.4) racc (~> 1.4)
oauth (0.5.4) oauth (0.5.4)
@@ -357,29 +355,29 @@ GEM
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (6.0.3.5) rails (6.0.3.6)
actioncable (= 6.0.3.5) actioncable (= 6.0.3.6)
actionmailbox (= 6.0.3.5) actionmailbox (= 6.0.3.6)
actionmailer (= 6.0.3.5) actionmailer (= 6.0.3.6)
actionpack (= 6.0.3.5) actionpack (= 6.0.3.6)
actiontext (= 6.0.3.5) actiontext (= 6.0.3.6)
actionview (= 6.0.3.5) actionview (= 6.0.3.6)
activejob (= 6.0.3.5) activejob (= 6.0.3.6)
activemodel (= 6.0.3.5) activemodel (= 6.0.3.6)
activerecord (= 6.0.3.5) activerecord (= 6.0.3.6)
activestorage (= 6.0.3.5) activestorage (= 6.0.3.6)
activesupport (= 6.0.3.5) activesupport (= 6.0.3.6)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 6.0.3.5) railties (= 6.0.3.6)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0) rails-html-sanitizer (1.3.0)
loofah (~> 2.3) loofah (~> 2.3)
railties (6.0.3.5) railties (6.0.3.6)
actionpack (= 6.0.3.5) actionpack (= 6.0.3.6)
activesupport (= 6.0.3.5) activesupport (= 6.0.3.6)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0) thor (>= 0.20.3, < 2.0)

View File

@@ -34,7 +34,9 @@ class ContactIdentifyAction
def update_contact def update_contact
custom_attributes = params[:custom_attributes] ? @contact.custom_attributes.merge(params[:custom_attributes]) : @contact.custom_attributes 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? ContactAvatarJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present?
end end

View File

@@ -32,7 +32,8 @@ class Api::V1::Widget::BaseController < ApplicationController
@contact_inbox = @web_widget.inbox.contact_inboxes.find_by( @contact_inbox = @web_widget.inbox.contact_inboxes.find_by(
source_id: auth_token_params[:source_id] source_id: auth_token_params[:source_id]
) )
@contact = @contact_inbox.contact @contact = @contact_inbox&.contact
raise ActiveRecord::RecordNotFound unless @contact
end end
def create_conversation def create_conversation

View File

@@ -0,0 +1,61 @@
<template>
<button
class="button"
:class="[
variant,
size,
colorScheme,
classNames,
isDisabled ? 'disabled' : '',
]"
:disabled="isDisabled || isLoading"
@click="handleClick"
>
<spinner v-if="isLoading" size="small" />
<i v-if="icon" :class="icon"></i>
<span v-if="$slots.default"><slot></slot></span>
</button>
</template>
<script>
import Spinner from 'shared/components/Spinner.vue';
export default {
name: 'WootButton',
components: { Spinner },
props: {
variant: {
type: String,
default: '',
},
size: {
type: String,
default: '',
},
icon: {
type: String,
default: '',
},
colorScheme: {
type: String,
default: 'primary',
},
classNames: {
type: String,
default: '',
},
isDisabled: {
type: Boolean,
default: false,
},
isLoading: {
type: Boolean,
default: false,
},
},
methods: {
handleClick(evt) {
this.$emit('click', evt);
},
},
};
</script>

View File

@@ -143,7 +143,9 @@ export default {
}, },
parsedLastMessage() { 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() { chatInbox() {

View File

@@ -107,11 +107,23 @@ export default {
this.contentAttributes, this.contentAttributes,
this.$t('CONVERSATION.NO_RESPONSE') 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() { contentAttributes() {
return this.data.content_attributes || {}; return this.data.content_attributes || {};

View File

@@ -87,14 +87,18 @@ export default {
}, },
computed: { computed: {
activeList() { activeList() {
return this.accountLabels.filter(accountLabel => return this.accountLabels
this.savedLabels.includes(accountLabel.title) .filter(accountLabel => this.savedLabels.includes(accountLabel.title))
); .sort((a, b) => {
return a.title.localeCompare(b.title);
});
}, },
inactiveList() { inactiveList() {
return this.accountLabels.filter( return this.accountLabels
accountLabel => !this.savedLabels.includes(accountLabel.title) .filter(accountLabel => !this.savedLabels.includes(accountLabel.title))
); .sort((a, b) => {
return a.title.localeCompare(b.title);
});
}, },
}, },
methods: { methods: {

View File

@@ -12,6 +12,7 @@ import hljs from 'highlight.js';
import Multiselect from 'vue-multiselect'; import Multiselect from 'vue-multiselect';
import WootSwitch from 'components/ui/Switch'; import WootSwitch from 'components/ui/Switch';
import WootWizard from 'components/ui/Wizard'; import WootWizard from 'components/ui/Wizard';
import WootButton from 'components/ui/WootButton';
import { sync } from 'vuex-router-sync'; import { sync } from 'vuex-router-sync';
import Vuelidate from 'vuelidate'; import Vuelidate from 'vuelidate';
import VTooltip from 'v-tooltip'; import VTooltip from 'v-tooltip';
@@ -48,6 +49,7 @@ Vue.use(hljs.vuePlugin);
Vue.component('multiselect', Multiselect); Vue.component('multiselect', Multiselect);
Vue.component('woot-switch', WootSwitch); Vue.component('woot-switch', WootSwitch);
Vue.component('woot-wizard', WootWizard); Vue.component('woot-wizard', WootWizard);
Vue.component('woot-button', WootButton);
const i18nConfig = new VueI18n({ const i18nConfig = new VueI18n({
locale: 'en', locale: 'en',

View File

@@ -49,10 +49,13 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
@import '~widget/assets/scss/mixins.scss'; @import '~widget/assets/scss/mixins.scss';
$logo-size: 56px;
.header-expanded { .header-expanded {
.logo { .logo {
width: 56px; width: $logo-size;
height: 56px; height: $logo-size;
border-radius: $logo-size;
} }
} }
</style> </style>

View File

@@ -71,7 +71,7 @@ export default {
if (workingHoursEnabled) { if (workingHoursEnabled) {
return this.outOfOfficeMessage; return this.outOfOfficeMessage;
} }
return this.$t('TEAM_AVAILABILITY.OFFLINE'); return '';
}, },
}, },
methods: { methods: {

View File

@@ -10,7 +10,7 @@
}, },
"TEAM_AVAILABILITY": { "TEAM_AVAILABILITY": {
"ONLINE": "We are online", "ONLINE": "We are online",
"OFFLINE": "We are offline" "OFFLINE": "We are away at the moment"
}, },
"REPLY_TIME": { "REPLY_TIME": {
"IN_A_FEW_MINUTES": "Typically replies in a few minutes", "IN_A_FEW_MINUTES": "Typically replies in a few minutes",

View File

@@ -6,7 +6,7 @@ class ApplicationMailbox < ActionMailbox::Base
def self.reply_mail? def self.reply_mail?
proc do |inbound_mail_obj| proc do |inbound_mail_obj|
is_a_reply_email = false 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] username = email.split('@')[0]
match_result = username.match(REPLY_EMAIL_USERNAME_PATTERN) match_result = username.match(REPLY_EMAIL_USERNAME_PATTERN)
if match_result if match_result
@@ -21,7 +21,7 @@ class ApplicationMailbox < ActionMailbox::Base
def self.support_mail? def self.support_mail?
proc do |inbound_mail_obj| proc do |inbound_mail_obj|
is_a_support_email = false 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) channel = Channel::Email.find_by('email = ? OR forward_to_email = ?', email, email)
if channel.present? if channel.present?
is_a_support_email = true is_a_support_email = true

View File

@@ -70,7 +70,7 @@ class Notification < ApplicationRecord
I18n.t( I18n.t(
'notifications.notification_title.assigned_conversation_new_message', 'notifications.notification_title.assigned_conversation_new_message',
display_id: conversation.display_id, display_id: conversation.display_id,
content: primary_actor.content.truncate_words(10) content: primary_actor.content&.truncate_words(10)
) )
when 'conversation_mention' when 'conversation_mention'
I18n.t('notifications.notification_title.conversation_mention', display_id: conversation.display_id, name: secondary_actor.name) I18n.t('notifications.notification_title.conversation_mention', display_id: conversation.display_id, name: secondary_actor.name)

View File

@@ -1,5 +1,5 @@
shared: &shared shared: &shared
version: '1.14.2' version: '1.14.3'
development: development:
<<: *shared <<: *shared

View File

@@ -1,4 +1,5 @@
module ExceptionList module ExceptionList
URI_EXCEPTIONS = [Errno::ETIMEDOUT, Errno::ECONNREFUSED, URI::InvalidURIError, Net::OpenTimeout, SocketError].freeze 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 end

View File

@@ -1,6 +1,6 @@
{ {
"name": "@chatwoot/chatwoot", "name": "@chatwoot/chatwoot",
"version": "1.14.2", "version": "1.14.3",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"eslint": "eslint app/javascript --fix", "eslint": "eslint app/javascript --fix",

View File

@@ -44,5 +44,15 @@ describe ::ContactIdentifyAction do
expect { contact.reload }.to raise_error(ActiveRecord::RecordNotFound) expect { contact.reload }.to raise_error(ActiveRecord::RecordNotFound)
end end
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
end end

View File

@@ -25,6 +25,21 @@ RSpec.describe '/api/v1/widget/conversations/toggle_typing', type: :request do
expect(json_response['status']).to eq(conversation.status) expect(json_response['status']).to eq(conversation.status)
end end
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 end
describe 'POST /api/v1/widget/conversations' do describe 'POST /api/v1/widget/conversations' do

View File

@@ -41,6 +41,14 @@ RSpec.describe Notification do
#{message.content.truncate_words(10)}" #{message.content.truncate_words(10)}"
end 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 it 'returns appropriate title suited for the notification type conversation_mention' do
message = create(:message, sender: create(:user)) message = create(:message, sender: create(:user))
notification = create(:notification, notification_type: 'conversation_mention', primary_actor: message, secondary_actor: message.sender) notification = create(:notification, notification_type: 'conversation_mention', primary_actor: message, secondary_actor: message.sender)