From f64cf85ab24e933e9469d0eab45c748f4f859f3e Mon Sep 17 00:00:00 2001 From: Tejaswini Chile Date: Mon, 9 May 2022 23:54:45 +0530 Subject: [PATCH 1/7] Fix: sentry issue for slack incoming files check (#4656) Interpreter error for nil. any? added nil. present? Fixes: https://sentry.io/share/issue/48c10d26490f4bdaab78c82244fcea98/ --- lib/integrations/slack/incoming_message_builder.rb | 2 +- .../integrations/slack/incoming_message_builder_spec.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/integrations/slack/incoming_message_builder.rb b/lib/integrations/slack/incoming_message_builder.rb index ec4f8e2b5..86c3c232b 100644 --- a/lib/integrations/slack/incoming_message_builder.rb +++ b/lib/integrations/slack/incoming_message_builder.rb @@ -37,7 +37,7 @@ class Integrations::Slack::IncomingMessageBuilder if message.present? SUPPORTED_MESSAGE_TYPES.include?(message[:type]) && !attached_file_message? else - params[:event][:files].any? && !attached_file_message? + params[:event][:files].present? && !attached_file_message? end end diff --git a/spec/lib/integrations/slack/incoming_message_builder_spec.rb b/spec/lib/integrations/slack/incoming_message_builder_spec.rb index 1695331c3..0eede30cf 100644 --- a/spec/lib/integrations/slack/incoming_message_builder_spec.rb +++ b/spec/lib/integrations/slack/incoming_message_builder_spec.rb @@ -61,6 +61,15 @@ describe Integrations::Slack::IncomingMessageBuilder do expect(conversation.messages.count).to eql(messages_count) end + it 'does not create message for invalid event type and event files is not present' do + messages_count = conversation.messages.count + message_with_attachments[:event][:files] = nil + builder = described_class.new(message_with_attachments) + allow(builder).to receive(:sender).and_return(nil) + builder.perform + expect(conversation.messages.count).to eql(messages_count) + end + it 'saves attachment if params files present' do expect(hook).not_to eq nil messages_count = conversation.messages.count From 81d0405473109971e24773657705fa4e77af49fb Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Tue, 10 May 2022 00:28:46 +0530 Subject: [PATCH 2/7] chore: Ability to update user email via Platform APIs (#4659) When the platform update API is called with a new user email, Chatwoot will still follow the same behaviour as in the dashboard where the user will have to confirm the new email activation link until the email gets updated on the user record. In the case of platform APIs, this might not be the ideal behaviour since the original app will already have a flow to update the user emails. Hence we need to confirm the emails without the extra step in this case fixes: #4510 --- app/controllers/platform/api/v1/users_controller.rb | 4 ++++ spec/controllers/platform/api/v1/users_controller_spec.rb | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/controllers/platform/api/v1/users_controller.rb b/app/controllers/platform/api/v1/users_controller.rb index c9f256c6f..4a7eafc9c 100644 --- a/app/controllers/platform/api/v1/users_controller.rb +++ b/app/controllers/platform/api/v1/users_controller.rb @@ -21,6 +21,10 @@ class Platform::Api::V1::UsersController < PlatformController def update @resource.assign_attributes(user_update_params) + + # We are using devise's reconfirmable flow for changing emails + # But in case of platform APIs we don't want user to go through this extra step + @resource.skip_reconfirmation! if user_update_params[:email].present? @resource.save! end diff --git a/spec/controllers/platform/api/v1/users_controller_spec.rb b/spec/controllers/platform/api/v1/users_controller_spec.rb index e2e9d17df..6ed3cbb7d 100644 --- a/spec/controllers/platform/api/v1/users_controller_spec.rb +++ b/spec/controllers/platform/api/v1/users_controller_spec.rb @@ -145,14 +145,17 @@ RSpec.describe 'Platform Users API', type: :request do expect(response).to have_http_status(:unauthorized) end - it 'updates the user' do + it 'updates the user attributes' do create(:platform_app_permissible, platform_app: platform_app, permissible: user) - patch "/platform/api/v1/users/#{user.id}", params: { name: 'test123', custom_attributes: { test: 'test_update' } }, + patch "/platform/api/v1/users/#{user.id}", params: { + name: 'test123', email: 'newtestemail@test.com', custom_attributes: { test: 'test_update' } + }, headers: { api_access_token: platform_app.access_token.token }, as: :json expect(response).to have_http_status(:success) data = JSON.parse(response.body) expect(data['name']).to eq('test123') + expect(data['email']).to eq('newtestemail@test.com') expect(data['custom_attributes']['test']).to eq('test_update') end end From 9ed1f5d96b558564bfcd5e3d1c6cc2b47790e33f Mon Sep 17 00:00:00 2001 From: Tejaswini Chile Date: Tue, 10 May 2022 19:20:55 +0530 Subject: [PATCH 3/7] Fix: Make version changeable from the environment vars (#4654) --- app/controllers/dashboard_controller.rb | 7 +++++-- app/javascript/dashboard/helper/scriptGenerator.js | 2 +- .../routes/dashboard/settings/inbox/channels/Facebook.vue | 2 +- .../dashboard/settings/inbox/facebook/Reauthorize.vue | 2 +- app/views/layouts/vueapp.html.erb | 1 + 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 2bb8e9847..107aec56e 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -38,9 +38,12 @@ class DashboardController < ActionController::Base end def app_config - { APP_VERSION: Chatwoot.config[:version], + { + APP_VERSION: Chatwoot.config[:version], VAPID_PUBLIC_KEY: VapidService.public_key, ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false'), - FB_APP_ID: GlobalConfigService.load('FB_APP_ID', '') } + FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''), + FACEBOOK_API_VERSION: 'v13.0' + } end end diff --git a/app/javascript/dashboard/helper/scriptGenerator.js b/app/javascript/dashboard/helper/scriptGenerator.js index 5a278d30a..bb6f2a72f 100644 --- a/app/javascript/dashboard/helper/scriptGenerator.js +++ b/app/javascript/dashboard/helper/scriptGenerator.js @@ -4,7 +4,7 @@ export const createMessengerScript = pageId => ` FB.init({ appId: "${window.chatwootConfig.fbAppId}", xfbml: true, - version: "v4.0" + version: "${window.chatwootConfig.fbApiVersion}" }); }; (function(d, s, id){ diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue index 820b4631b..31eff872e 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue @@ -163,7 +163,7 @@ export default { FB.init({ appId: window.chatwootConfig.fbAppId, xfbml: true, - version: 'v12.0', + version: window.chatwootConfig.fbApiVersion, status: true, }); window.fbSDKLoaded = true; diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/facebook/Reauthorize.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/facebook/Reauthorize.vue index 736642f29..d86ebaaff 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/facebook/Reauthorize.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/facebook/Reauthorize.vue @@ -40,7 +40,7 @@ export default { FB.init({ appId: window.chatwootConfig.fbAppId, xfbml: true, - version: 'v12.0', + version: window.chatwootConfig.fbApiVersion, status: true, }); window.fbSDKLoaded = true; diff --git a/app/views/layouts/vueapp.html.erb b/app/views/layouts/vueapp.html.erb index f7d7155d1..3f91ac042 100644 --- a/app/views/layouts/vueapp.html.erb +++ b/app/views/layouts/vueapp.html.erb @@ -34,6 +34,7 @@ window.chatwootConfig = { hostURL: '<%= ENV.fetch('FRONTEND_URL', '') %>', fbAppId: '<%= ENV.fetch('FB_APP_ID', nil) %>', + fbApiVersion: '<%= @global_config['FACEBOOK_API_VERSION'] %>', signupEnabled: '<%= @global_config['ENABLE_ACCOUNT_SIGNUP'] %>', <% if @global_config['VAPID_PUBLIC_KEY'] %> vapidPublicKey: new Uint8Array(<%= Base64.urlsafe_decode64(@global_config['VAPID_PUBLIC_KEY']).bytes %>), From 9cec0917165a21651b335f8beac10f259da1ba56 Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Wed, 11 May 2022 12:05:42 +0530 Subject: [PATCH 4/7] chore: Remove unused method scriptGenerator (#4671) --- .../dashboard/helper/scriptGenerator.js | 25 ------------------- .../dashboard/settings/inbox/Settings.vue | 4 --- 2 files changed, 29 deletions(-) delete mode 100644 app/javascript/dashboard/helper/scriptGenerator.js diff --git a/app/javascript/dashboard/helper/scriptGenerator.js b/app/javascript/dashboard/helper/scriptGenerator.js deleted file mode 100644 index bb6f2a72f..000000000 --- a/app/javascript/dashboard/helper/scriptGenerator.js +++ /dev/null @@ -1,25 +0,0 @@ -export const createMessengerScript = pageId => ` - -
-
-`; diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue index 16b6b0f74..38a1c5e98 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue @@ -436,7 +436,6 @@ diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/CollaboratorsPage.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/CollaboratorsPage.vue new file mode 100644 index 000000000..2b3fa413f --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/CollaboratorsPage.vue @@ -0,0 +1,148 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/ConfigurationPage.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/ConfigurationPage.vue new file mode 100644 index 000000000..cc8746fed --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/ConfigurationPage.vue @@ -0,0 +1,130 @@ + + + diff --git a/lib/seeders/account_seeder.rb b/lib/seeders/account_seeder.rb index 65fe5a4e1..162bc4cd9 100644 --- a/lib/seeders/account_seeder.rb +++ b/lib/seeders/account_seeder.rb @@ -17,6 +17,7 @@ class Seeders::AccountSeeder def seed! seed_canned_responses + seed_inboxes end def seed_canned_responses(count: 50) @@ -24,4 +25,65 @@ class Seeders::AccountSeeder account.canned_responses.create(content: Faker::Quote.fortune_cookie, short_code: Faker::Alphanumeric.alpha(number: 10)) end end + + def seed_inboxes + seed_website_inbox + seed_facebook_inbox + seed_twitter_inbox + seed_whatsapp_inbox + seed_sms_inbox + seed_email_inbox + seed_api_inbox + seed_telegram_inbox + seed_line_inbox + end + + def seed_website_inbox + channel = Channel::WebWidget.create!(account: account, website_url: 'https://acme.inc') + Inbox.create!(channel: channel, account: account, name: 'Acme Website') + end + + def seed_facebook_inbox + channel = Channel::FacebookPage.create!(account: account, user_access_token: 'test', page_access_token: 'test', page_id: 'test') + Inbox.create!(channel: channel, account: account, name: 'Acme Facebook') + end + + def seed_twitter_inbox + channel = Channel::TwitterProfile.create!(account: account, twitter_access_token: 'test', twitter_access_token_secret: 'test', profile_id: '123') + Inbox.create!(channel: channel, account: account, name: 'Acme Twitter') + end + + def seed_whatsapp_inbox + channel = Channel::Whatsapp.create!(account: account, phone_number: '+123456789') + Inbox.create!(channel: channel, account: account, name: 'Acme Whatsapp') + end + + def seed_sms_inbox + channel = Channel::Sms.create!(account: account, phone_number: '+123456789') + Inbox.create!(channel: channel, account: account, name: 'Acme SMS') + end + + def seed_email_inbox + channel = Channel::Email.create!(account: account, email: 'test@acme.inc', forward_to_email: 'test_fwd@acme.inc') + Inbox.create!(channel: channel, account: account, name: 'Acme Email') + end + + def seed_api_inbox + channel = Channel::Api.create!(account: account) + Inbox.create!(channel: channel, account: account, name: 'Acme API') + end + + def seed_telegram_inbox + # rubocop:disable Rails/SkipsModelValidations + Channel::Telegram.insert({ account_id: account.id, bot_name: 'Acme', bot_token: 'test', created_at: Time.now.utc, updated_at: Time.now.utc }, + returning: %w[id]) + channel = Channel::Telegram.find_by(bot_token: 'test') + Inbox.create!(channel: channel, account: account, name: 'Acme Telegram') + # rubocop:enable Rails/SkipsModelValidations + end + + def seed_line_inbox + channel = Channel::Line.create!(account: account, line_channel_id: 'test', line_channel_secret: 'test', line_channel_token: 'test') + Inbox.create!(channel: channel, account: account, name: 'Acme Line') + end end From 329d305e9272ae62113a3cfd6564500e5c5d1cca Mon Sep 17 00:00:00 2001 From: Tejaswini Chile Date: Wed, 11 May 2022 14:29:38 +0530 Subject: [PATCH 6/7] Fix: Creating contacts for already outgoing/echo messages (#4668) Check if the Instagram contact is already present in the system before throwing the exception. Fixes: #4666 --- app/services/instagram/message_text.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/services/instagram/message_text.rb b/app/services/instagram/message_text.rb index 75c77b540..619ef4c90 100644 --- a/app/services/instagram/message_text.rb +++ b/app/services/instagram/message_text.rb @@ -22,7 +22,7 @@ class Instagram::MessageText < Instagram::WebhooksBaseService return unsend_message if message_is_deleted? - ensure_contact(contact_id) + ensure_contact(contact_id) if contacts_first_message?(contact_id) create_message end @@ -36,7 +36,7 @@ class Instagram::MessageText < Instagram::WebhooksBaseService rescue Koala::Facebook::AuthenticationError @inbox.channel.authorization_error! raise - rescue StandardError => e + rescue StandardError, Koala::Facebook::ClientError => e result = {} ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception end @@ -52,6 +52,10 @@ class Instagram::MessageText < Instagram::WebhooksBaseService @messaging[:message][:is_deleted].present? end + def contacts_first_message?(ig_scope_id) + @inbox.contact_inboxes.where(source_id: ig_scope_id).empty? && @inbox.channel.instagram_id.present? + end + def unsend_message message_to_delete = @inbox.messages.find_by( source_id: @messaging[:message][:mid] From 41b89014324772d8351b15936d25623f852980cb Mon Sep 17 00:00:00 2001 From: "Aswin Dev P.S" Date: Wed, 11 May 2022 14:31:57 +0530 Subject: [PATCH 7/7] Fix: Agent Reports counts when they have access to multiple accounts (#4663) This change restricts the agent report API to fetch agent metrics from the current account. Fixes: #4660 --- app/helpers/report_helper.rb | 16 ++++----- .../api/v2/accounts/report_controller_spec.rb | 34 +++++++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/app/helpers/report_helper.rb b/app/helpers/report_helper.rb index 9f37295cb..5fdb34170 100644 --- a/app/helpers/report_helper.rb +++ b/app/helpers/report_helper.rb @@ -17,30 +17,30 @@ module ReportHelper end def conversations_count - (get_grouped_values scope.conversations).count + (get_grouped_values scope.conversations.where(account_id: account.id)).count end def incoming_messages_count - (get_grouped_values scope.messages.incoming.unscope(:order)).count + (get_grouped_values scope.messages.where(account_id: account.id).incoming.unscope(:order)).count end def outgoing_messages_count - (get_grouped_values scope.messages.outgoing.unscope(:order)).count + (get_grouped_values scope.messages.where(account_id: account.id).outgoing.unscope(:order)).count end def resolutions_count - (get_grouped_values scope.conversations.resolved).count + (get_grouped_values scope.conversations.where(account_id: account.id).resolved).count end def avg_first_response_time - grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'first_response')) + grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'first_response', account_id: account.id)) return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours] grouped_reporting_events.average(:value) end def avg_resolution_time - grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'conversation_resolved')) + grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'conversation_resolved', account_id: account.id)) return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours] grouped_reporting_events.average(:value) @@ -48,7 +48,7 @@ module ReportHelper def avg_resolution_time_summary reporting_events = scope.reporting_events - .where(name: 'conversation_resolved', created_at: range) + .where(name: 'conversation_resolved', account_id: account.id, created_at: range) avg_rt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value) return 0 if avg_rt.blank? @@ -58,7 +58,7 @@ module ReportHelper def avg_first_response_time_summary reporting_events = scope.reporting_events - .where(name: 'first_response', created_at: range) + .where(name: 'first_response', account_id: account.id, created_at: range) avg_frt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value) return 0 if avg_frt.blank? diff --git a/spec/controllers/api/v2/accounts/report_controller_spec.rb b/spec/controllers/api/v2/accounts/report_controller_spec.rb index c14b570d2..134a61220 100644 --- a/spec/controllers/api/v2/accounts/report_controller_spec.rb +++ b/spec/controllers/api/v2/accounts/report_controller_spec.rb @@ -223,6 +223,40 @@ RSpec.describe 'Reports API', type: :request do expect(response).to have_http_status(:success) end end + + context 'when an agent has access to multiple accounts' do + let(:account1) { create(:account) } + let(:account2) { create(:account) } + + let(:params) do + super().merge( + type: :agent, + since: 30.days.ago.to_i.to_s, + until: date_timestamp.to_s + ) + end + + it 'returns agent metrics from the current account' do + admin1 = create(:user, account: account1, role: :administrator) + inbox1 = create(:inbox, account: account1) + inbox2 = create(:inbox, account: account2) + + create(:account_user, user: admin1, account: account2) + create(:conversation, account: account1, inbox: inbox1, + assignee: admin1, created_at: Time.zone.today - 2.days) + create(:conversation, account: account2, inbox: inbox2, + assignee: admin1, created_at: Time.zone.today - 2.days) + + get "/api/v2/accounts/#{account1.id}/reports/summary", + params: params.merge({ id: admin1.id }), + headers: admin1.create_new_auth_token + + expect(response).to have_http_status(:success) + + json_response = JSON.parse(response.body) + expect(json_response['conversations_count']).to eq(1) + end + end end describe 'GET /api/v2/accounts/:account_id/reports/inboxes' do