From 6c8dacfa0d40428e6727162255c316c9c8491fd9 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Mon, 20 Nov 2023 12:22:45 +0530 Subject: [PATCH 01/63] feat: Facebook delivery reports (#8136) --- app/jobs/webhooks/facebook_delivery_job.rb | 8 ++ config/initializers/facebook_messenger.rb | 15 ++-- lib/integrations/facebook/delivery_status.rb | 29 ++++--- lib/integrations/facebook/message_parser.rb | 16 ++++ .../incoming_fb_text_message.rb | 20 +++++ .../webhooks/facebook_delivery_job_spec.rb | 44 ++++++++++ .../facebook/delivery_status_spec.rb | 83 +++++++++++++++++++ 7 files changed, 195 insertions(+), 20 deletions(-) create mode 100644 app/jobs/webhooks/facebook_delivery_job.rb create mode 100644 spec/jobs/webhooks/facebook_delivery_job_spec.rb create mode 100644 spec/lib/integrations/facebook/delivery_status_spec.rb diff --git a/app/jobs/webhooks/facebook_delivery_job.rb b/app/jobs/webhooks/facebook_delivery_job.rb new file mode 100644 index 000000000..e6b7d1dcf --- /dev/null +++ b/app/jobs/webhooks/facebook_delivery_job.rb @@ -0,0 +1,8 @@ +class Webhooks::FacebookDeliveryJob < ApplicationJob + queue_as :low + + def perform(message) + response = ::Integrations::Facebook::MessageParser.new(message) + Integrations::Facebook::DeliveryStatus.new(params: response).perform + end +end diff --git a/config/initializers/facebook_messenger.rb b/config/initializers/facebook_messenger.rb index f9f4fae88..354687cbd 100644 --- a/config/initializers/facebook_messenger.rb +++ b/config/initializers/facebook_messenger.rb @@ -29,14 +29,13 @@ Rails.application.reloader.to_prepare do end Facebook::Messenger::Bot.on :delivery do |delivery| - # delivery.ids # => 'mid.1457764197618:41d102a3e1ae206a38' - # delivery.sender # => { 'id' => '1008372609250235' } - # delivery.recipient # => { 'id' => '2015573629214912' } - # delivery.at # => 2016-04-22 21:30:36 +0200 - # delivery.seq # => 37 - updater = Integrations::Facebook::DeliveryStatus.new(delivery) - updater.perform - Rails.logger.info "Human was online at #{delivery.at}" + Rails.logger.info "Recieved delivery status #{delivery.to_json}" + Webhooks::FacebookDeliveryJob.perform_later(delivery.to_json) + end + + Facebook::Messenger::Bot.on :read do |read| + Rails.logger.info "Recieved read status #{read.to_json}" + Webhooks::FacebookDeliveryJob.perform_later(read.to_json) end Facebook::Messenger::Bot.on :message_echo do |message| diff --git a/lib/integrations/facebook/delivery_status.rb b/lib/integrations/facebook/delivery_status.rb index d38ece092..1d6257bba 100644 --- a/lib/integrations/facebook/delivery_status.rb +++ b/lib/integrations/facebook/delivery_status.rb @@ -1,32 +1,37 @@ # frozen_string_literal: true class Integrations::Facebook::DeliveryStatus - def initialize(params) - @params = params - end + pattr_initialize [:params!] def perform - update_message_status + return if facebook_channel.blank? + return unless conversation + + process_delivery_status if params.delivery_watermark + process_read_status if params.read_watermark end private - def sender_id - @params.sender['id'] + def process_delivery_status + timestamp = Time.zone.at(params.delivery_watermark.to_i).to_datetime.utc + ::Conversations::UpdateMessageStatusJob.perform_later(conversation.id, timestamp, :delivered) + end + + def process_read_status + timestamp = Time.zone.at(params.read_watermark.to_i).to_datetime.utc + ::Conversations::UpdateMessageStatusJob.perform_later(conversation.id, timestamp, :read) end def contact - ::ContactInbox.find_by(source_id: sender_id)&.contact + ::ContactInbox.find_by(source_id: params.sender_id)&.contact end def conversation @conversation ||= ::Conversation.find_by(contact_id: contact.id) if contact.present? end - def update_message_status - return unless conversation - - conversation.contact_last_seen_at = @params.at - conversation.save! + def facebook_channel + @facebook_channel ||= Channel::FacebookPage.find_by(page_id: params.recipient_id) end end diff --git a/lib/integrations/facebook/message_parser.rb b/lib/integrations/facebook/message_parser.rb index 123b521cb..275a46c39 100644 --- a/lib/integrations/facebook/message_parser.rb +++ b/lib/integrations/facebook/message_parser.rb @@ -34,6 +34,22 @@ class Integrations::Facebook::MessageParser @messaging.dig('message', 'mid') end + def delivery + @messaging['delivery'] + end + + def read + @messaging['read'] + end + + def read_watermark + read&.dig('watermark') + end + + def delivery_watermark + delivery&.dig('watermark') + end + def echo? @messaging.dig('message', 'is_echo') end diff --git a/spec/factories/facebook_message/incoming_fb_text_message.rb b/spec/factories/facebook_message/incoming_fb_text_message.rb index 83d516f09..dc75a912c 100644 --- a/spec/factories/facebook_message/incoming_fb_text_message.rb +++ b/spec/factories/facebook_message/incoming_fb_text_message.rb @@ -10,4 +10,24 @@ FactoryBot.define do initialize_with { attributes } end + + factory :message_deliveries, class: Hash do + messaging do + { sender: { id: '3383290475046708' }, + recipient: { id: '117172741761305' }, + delivery: { watermark: '1648581633369' } } + end + + initialize_with { attributes } + end + + factory :message_reads, class: Hash do + messaging do + { sender: { id: '3383290475046708' }, + recipient: { id: '117172741761305' }, + read: { watermark: '1648581633369' } } + end + + initialize_with { attributes } + end end diff --git a/spec/jobs/webhooks/facebook_delivery_job_spec.rb b/spec/jobs/webhooks/facebook_delivery_job_spec.rb new file mode 100644 index 000000000..8dc91b9ab --- /dev/null +++ b/spec/jobs/webhooks/facebook_delivery_job_spec.rb @@ -0,0 +1,44 @@ +require 'rails_helper' + +RSpec.describe Webhooks::FacebookDeliveryJob do + include ActiveJob::TestHelper + + let(:message) { 'test_message' } + let(:parsed_message) { instance_double(Integrations::Facebook::MessageParser) } + let(:delivery_status) { instance_double(Integrations::Facebook::DeliveryStatus) } + + before do + allow(Integrations::Facebook::MessageParser).to receive(:new).with(message).and_return(parsed_message) + allow(Integrations::Facebook::DeliveryStatus).to receive(:new).with(params: parsed_message).and_return(delivery_status) + allow(delivery_status).to receive(:perform) + end + + after do + clear_enqueued_jobs + end + + describe '#perform_later' do + it 'enqueues the job' do + expect do + described_class.perform_later(message) + end.to have_enqueued_job(described_class).with(message).on_queue('low') + end + end + + describe '#perform' do + it 'calls the MessageParser with the correct argument' do + expect(Integrations::Facebook::MessageParser).to receive(:new).with(message) + described_class.perform_now(message) + end + + it 'calls the DeliveryStatus with the correct argument' do + expect(Integrations::Facebook::DeliveryStatus).to receive(:new).with(params: parsed_message) + described_class.perform_now(message) + end + + it 'executes perform on the DeliveryStatus instance' do + expect(delivery_status).to receive(:perform) + described_class.perform_now(message) + end + end +end diff --git a/spec/lib/integrations/facebook/delivery_status_spec.rb b/spec/lib/integrations/facebook/delivery_status_spec.rb new file mode 100644 index 000000000..28f1fe982 --- /dev/null +++ b/spec/lib/integrations/facebook/delivery_status_spec.rb @@ -0,0 +1,83 @@ +require 'rails_helper' + +describe Integrations::Facebook::DeliveryStatus do + subject(:message_builder) { described_class.new(message_deliveries, facebook_channel.inbox).perform } + + before do + stub_request(:post, /graph\.facebook\.com/) + end + + let!(:account) { create(:account) } + let!(:facebook_channel) { create(:channel_facebook_page, page_id: '117172741761305') } + let!(:message_delivery_object) { build(:message_deliveries).to_json } + let!(:message_deliveries) { Integrations::Facebook::MessageParser.new(message_delivery_object) } + + let!(:contact) { create(:contact, account: account) } + let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: facebook_channel.inbox, source_id: '3383290475046708') } + let!(:conversation) { create(:conversation, inbox: facebook_channel.inbox, contact: contact, contact_inbox: contact_inbox) } + + let!(:message_read_object) { build(:message_reads).to_json } + let!(:message_reads) { Integrations::Facebook::MessageParser.new(message_read_object) } + let!(:message1) do + create(:message, content: 'facebook message', message_type: 'outgoing', inbox: facebook_channel.inbox, conversation: conversation) + end + let!(:message2) do + create(:message, content: 'facebook message', message_type: 'incoming', inbox: facebook_channel.inbox, conversation: conversation) + end + + describe '#perform' do + context 'when message_deliveries callback fires' do + before do + allow(Conversations::UpdateMessageStatusJob).to receive(:perform_later) + end + + it 'updates all messages if the status is delivered' do + described_class.new(params: message_deliveries).perform + expect(Conversations::UpdateMessageStatusJob).to have_received(:perform_later).with( + message1.conversation.id, + Time.zone.at(message_deliveries.delivery['watermark'].to_i).to_datetime, + :delivered + ) + end + + it 'does not update the message status if the message is incoming' do + described_class.new(params: message_deliveries).perform + expect(message2.reload.status).to eq('sent') + end + + it 'does not update the message status if the message was created after the watermark' do + message1.update(created_at: 1.day.from_now) + message_deliveries.delivery['watermark'] = 1.day.ago.to_i + described_class.new(params: message_deliveries).perform + expect(message1.reload.status).to eq('sent') + end + end + + context 'when message_reads callback fires' do + before do + allow(Conversations::UpdateMessageStatusJob).to receive(:perform_later) + end + + it 'updates all messages if the status is read' do + described_class.new(params: message_reads).perform + expect(Conversations::UpdateMessageStatusJob).to have_received(:perform_later).with( + message1.conversation.id, + Time.zone.at(message_reads.read['watermark'].to_i).to_datetime, + :read + ) + end + + it 'does not update the message status if the message is incoming' do + described_class.new(params: message_reads).perform + expect(message2.reload.status).to eq('sent') + end + + it 'does not update the message status if the message was created after the watermark' do + message1.update(created_at: 1.day.from_now) + message_reads.read['watermark'] = 1.day.ago.to_i + described_class.new(params: message_reads).perform + expect(message1.reload.status).to eq('sent') + end + end + end +end From 5b3f9ac1cd83acde53e7f76d6562f54652cf9289 Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Mon, 20 Nov 2023 12:48:39 +0530 Subject: [PATCH 02/63] chore: skip node upgrade if latest on cwctl upgrade (#8336) --- deployment/setup_20.04.sh | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/deployment/setup_20.04.sh b/deployment/setup_20.04.sh index 5e2d3c250..411e43e85 100644 --- a/deployment/setup_20.04.sh +++ b/deployment/setup_20.04.sh @@ -754,8 +754,31 @@ function upgrade_redis() { apt install libvips -y } + +############################################################################## +# Update nodejs to v20+ +# Globals: +# None +# Arguments: +# None +# Outputs: +# None +############################################################################## function upgrade_node() { - echo "Upgrading nodejs version to v20.x" + echo "Checking Node.js version..." + + # Get current Node.js version + current_version=$(node --version | cut -c 2-) + + # Parse major version number + major_version=$(echo "$current_version" | cut -d. -f1) + + if [ "$major_version" -ge 20 ]; then + echo "Node.js is already version $current_version (>= 20.x). Skipping Node.js upgrade." + return + fi + + echo "Upgrading Node.js version to v20.x" curl -sL https://deb.nodesource.com/setup_20.x | sudo bash - apt install -y nodejs } From e750ee6d286cc831016e095d071463d051bbd98f Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Mon, 20 Nov 2023 19:42:54 -0800 Subject: [PATCH 03/63] chore: [Snyk] Security upgrade administrate-field-active_storage from 0.4.2 to 1.0.0 (#8382) Co-authored-by: snyk-bot --- Gemfile | 2 +- Gemfile.lock | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Gemfile b/Gemfile index a40b0f816..61cac1792 100644 --- a/Gemfile +++ b/Gemfile @@ -75,7 +75,7 @@ gem 'jwt' gem 'pundit' # super admin gem 'administrate', '>= 0.19.0' -gem 'administrate-field-active_storage' +gem 'administrate-field-active_storage', '>= 1.0.0' gem 'administrate-field-belongs_to_search' ##--- gems for pubsub service ---## diff --git a/Gemfile.lock b/Gemfile.lock index 8c782dd48..0a0916d6f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -113,7 +113,7 @@ GEM kaminari (>= 1.0) sassc-rails (~> 2.1) selectize-rails (~> 0.6) - administrate-field-active_storage (0.4.2) + administrate-field-active_storage (1.0.0) administrate (>= 0.2.2) rails (>= 7.0) administrate-field-belongs_to_search (0.8.0) @@ -183,7 +183,7 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.3.3) + date (3.3.4) ddtrace (1.11.1) debase-ruby_core_source (>= 0.10.16, <= 3.2.0) libdatadog (~> 2.0.0.1.0) @@ -256,7 +256,7 @@ GEM fcm (1.0.8) faraday (>= 1.0.0, < 3.0) googleauth (~> 1) - ffi (1.15.5) + ffi (1.16.3) ffi-compiler (1.0.1) ffi (>= 1.0.0) rake @@ -440,7 +440,7 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.21.4) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -472,14 +472,14 @@ GEM activerecord (>= 5.2) net-http-persistent (4.0.2) connection_pool (~> 2.2) - net-imap (0.3.7) + net-imap (0.4.5) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout - net-smtp (0.3.3) + net-smtp (0.4.0) net-protocol netrc (0.11.0) newrelic-sidekiq-metrics (1.6.2) @@ -487,15 +487,15 @@ GEM sidekiq newrelic_rpm (9.6.0) base64 - nio4r (2.5.9) - nokogiri (1.15.4) + nio4r (2.6.0) + nokogiri (1.15.5) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.15.4-arm64-darwin) + nokogiri (1.15.5-arm64-darwin) racc (~> 1.4) - nokogiri (1.15.4-x86_64-darwin) + nokogiri (1.15.5-x86_64-darwin) racc (~> 1.4) - nokogiri (1.15.4-x86_64-linux) + nokogiri (1.15.5-x86_64-linux) racc (~> 1.4) numo-narray (0.9.2.1) oauth (1.1.0) @@ -752,7 +752,7 @@ GEM spring-watcher-listen (2.1.0) listen (>= 2.7, < 4.0) spring (>= 4) - sprockets (4.2.0) + sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) sprockets-rails (3.4.2) @@ -766,11 +766,11 @@ GEM telephone_number (1.4.20) test-prof (1.2.1) thor (1.3.0) - tilt (2.2.0) + tilt (2.3.0) time_diff (0.3.0) activesupport i18n - timeout (0.4.0) + timeout (0.4.1) trailblazer-option (0.1.2) twilio-ruby (5.77.0) faraday (>= 0.9, < 3.0) @@ -841,7 +841,7 @@ DEPENDENCIES activerecord-import acts-as-taggable-on administrate (>= 0.19.0) - administrate-field-active_storage + administrate-field-active_storage (>= 1.0.0) administrate-field-belongs_to_search annotate attr_extras From 31c709be5caffb675177eea866dcba09ca36fb13 Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Wed, 22 Nov 2023 01:57:58 +0530 Subject: [PATCH 04/63] fix: Opens foreign links from article page in new tab [cw-2725] (#8304) Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> --- app/javascript/portal/portalHelpers.js | 39 ++++++++++++++++++++++++++ app/views/layouts/portal.html.erb | 2 ++ 2 files changed, 41 insertions(+) diff --git a/app/javascript/portal/portalHelpers.js b/app/javascript/portal/portalHelpers.js index de1a36548..1f0f24f66 100644 --- a/app/javascript/portal/portalHelpers.js +++ b/app/javascript/portal/portalHelpers.js @@ -68,6 +68,44 @@ export const updateThemeStyles = theme => { setPortalClass(theme); }; +export const openExternalLinksInNewTab = () => { + const { customDomain, hostURL } = window.portalConfig; + const isSameHost = + window.location.href.includes(customDomain) || + window.location.href.includes(hostURL); + + // Modify external links only on articles page + const isOnArticlePage = + isSameHost && document.querySelector('#cw-article-content') !== null; + + document.addEventListener('click', function (event) { + if (!isOnArticlePage) return; + + // Some of the links come wrapped in strong tag through prosemirror + + const isTagAnchor = event.target.tagName === 'A'; + const isParentTagAnchor = + event.target.tagName === 'STRONG' && + event.target.parentNode.tagName === 'A'; + + if (isTagAnchor || isParentTagAnchor) { + const link = isTagAnchor ? event.target : event.target.parentNode; + + const isInternalLink = + link.hostname === window.location.hostname || + link.href.includes(customDomain) || + link.href.includes(hostURL); + + if (!isInternalLink) { + link.target = '_blank'; + link.rel = 'noopener noreferrer'; // Security and performance benefits + // Prevent default if you want to stop the link from opening in the current tab + event.stopPropagation(); + } + } + }); +}; + export const toggleAppearanceDropdown = () => { const dropdown = document.getElementById('appearance-dropdown'); if (!dropdown) return; @@ -183,6 +221,7 @@ export const InitializationHelpers = { }, initialize: () => { + openExternalLinksInNewTab(); if (window.portalConfig.isPlainLayoutEnabled === 'true') { InitializationHelpers.appendPlainParamToURLs(); } else { diff --git a/app/views/layouts/portal.html.erb b/app/views/layouts/portal.html.erb index b6ec6d435..19295aa95 100644 --- a/app/views/layouts/portal.html.erb +++ b/app/views/layouts/portal.html.erb @@ -50,6 +50,8 @@ By default, it renders: window.portalConfig = { portalSlug: '<%= @portal.slug %>', portalColor: '<%= @portal.color %>', + customDomain: '<%= @portal.custom_domain %>', + hostURL: '<%= ENV.fetch('FRONTEND_URL', '') %>', theme: '<%= @theme %>', localeCode: '<%= @locale %>', searchTranslations: { From 9c7148e2ad49bae15df28dfe44bd167bbe2c6264 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Tue, 21 Nov 2023 14:51:05 -0800 Subject: [PATCH 05/63] feat: Split dashboard to chunks for build performance (#8394) Co-authored-by: Pranav Raj S --- .../dashboard/modules/search/search.routes.js | 3 ++- .../routes/dashboard/contacts/routes.js | 4 ++-- .../conversation/conversation.routes.js | 2 +- .../routes/dashboard/dashboard.routes.js | 4 ++-- .../routes/dashboard/notifications/routes.js | 4 ++-- .../settings/account/account.routes.js | 4 ++-- .../settings/agentBots/agentBot.routes.js | 2 +- .../dashboard/settings/agents/agent.routes.js | 4 ++-- .../settings/attributes/attributes.routes.js | 4 ++-- .../settings/auditlogs/audit.routes.js | 5 +++-- .../settings/automation/automation.routes.js | 4 ++-- .../settings/billing/billing.routes.js | 4 ++-- .../settings/campaigns/campaigns.routes.js | 4 ++-- .../settings/canned/canned.routes.js | 5 +++-- .../dashboard/settings/inbox/inbox.routes.js | 17 +++++++++-------- .../integrationapps/integrations.routes.js | 6 +++--- .../integrations/integrations.routes.js | 13 +++++++------ .../settings/labels/labels.routes.js | 5 +++-- .../settings/macros/macros.routes.js | 7 ++++--- .../settings/profile/profile.routes.js | 5 +++-- .../settings/reports/reports.routes.js | 17 +++++++++-------- .../dashboard/settings/teams/teams.routes.js | 19 ++++++++++--------- 22 files changed, 76 insertions(+), 66 deletions(-) diff --git a/app/javascript/dashboard/modules/search/search.routes.js b/app/javascript/dashboard/modules/search/search.routes.js index a805d2316..320f64a44 100644 --- a/app/javascript/dashboard/modules/search/search.routes.js +++ b/app/javascript/dashboard/modules/search/search.routes.js @@ -1,7 +1,8 @@ /* eslint-disable storybook/default-exports */ -import SearchView from './components/SearchView.vue'; import { frontendURL } from '../../helper/URLHelper'; +const SearchView = () => import('./components/SearchView.vue'); + export const routes = [ { path: frontendURL('accounts/:accountId/search'), diff --git a/app/javascript/dashboard/routes/dashboard/contacts/routes.js b/app/javascript/dashboard/routes/dashboard/contacts/routes.js index 9734c88e2..10a560740 100644 --- a/app/javascript/dashboard/routes/dashboard/contacts/routes.js +++ b/app/javascript/dashboard/routes/dashboard/contacts/routes.js @@ -1,7 +1,7 @@ /* eslint arrow-body-style: 0 */ -import ContactsView from './components/ContactsView'; -import ContactManageView from './pages/ContactManageView'; import { frontendURL } from '../../../helper/URLHelper'; +const ContactsView = () => import('./components/ContactsView.vue'); +const ContactManageView = () => import('./pages/ContactManageView.vue'); export const routes = [ { diff --git a/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js b/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js index d63de6cdf..14487682e 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js +++ b/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js @@ -1,6 +1,6 @@ /* eslint arrow-body-style: 0 */ -import ConversationView from './ConversationView'; import { frontendURL } from '../../../helper/URLHelper'; +const ConversationView = () => import('./ConversationView'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/dashboard.routes.js b/app/javascript/dashboard/routes/dashboard/dashboard.routes.js index 131789fa0..712e05928 100644 --- a/app/javascript/dashboard/routes/dashboard/dashboard.routes.js +++ b/app/javascript/dashboard/routes/dashboard/dashboard.routes.js @@ -1,4 +1,3 @@ -import AppContainer from './Dashboard'; import settings from './settings/settings.routes'; import conversation from './conversation/conversation.routes'; import { routes as searchRoutes } from '../../modules/search/search.routes'; @@ -7,7 +6,8 @@ import { routes as notificationRoutes } from './notifications/routes'; import { frontendURL } from '../../helper/URLHelper'; import helpcenterRoutes from './helpcenter/helpcenter.routes'; -const Suspended = () => import('./suspended/Index'); +const AppContainer = () => import('./Dashboard.vue'); +const Suspended = () => import('./suspended/Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/notifications/routes.js b/app/javascript/dashboard/routes/dashboard/notifications/routes.js index de3411f57..38812fc92 100644 --- a/app/javascript/dashboard/routes/dashboard/notifications/routes.js +++ b/app/javascript/dashboard/routes/dashboard/notifications/routes.js @@ -1,7 +1,7 @@ /* eslint arrow-body-style: 0 */ -import NotificationsView from './components/NotificationsView.vue'; import { frontendURL } from '../../../helper/URLHelper'; -import SettingsWrapper from '../settings/Wrapper'; +const SettingsWrapper = () => import('../settings/Wrapper.vue'); +const NotificationsView = () => import('./components/NotificationsView.vue'); export const routes = [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js b/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js index 5e3df43b4..0acfd5bd5 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js @@ -1,6 +1,6 @@ -import SettingsContent from '../Wrapper'; -import Index from './Index.vue'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Index = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/agentBots/agentBot.routes.js b/app/javascript/dashboard/routes/dashboard/settings/agentBots/agentBot.routes.js index e06f18a0b..382594c73 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/agentBots/agentBot.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/agentBots/agentBot.routes.js @@ -1,8 +1,8 @@ -import SettingsContent from '../Wrapper'; const Bot = () => import('./Index.vue'); const CsmlEditBot = () => import('./csml/Edit.vue'); const CsmlNewBot = () => import('./csml/New.vue'); import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/agents/agent.routes.js b/app/javascript/dashboard/routes/dashboard/settings/agents/agent.routes.js index 3bf4b6d2e..cd23432ff 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/agents/agent.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/agents/agent.routes.js @@ -1,6 +1,6 @@ -import SettingsContent from '../Wrapper'; -import AgentHome from './Index'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const AgentHome = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/attributes/attributes.routes.js b/app/javascript/dashboard/routes/dashboard/settings/attributes/attributes.routes.js index a4430ff1e..72d79a9de 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/attributes/attributes.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/attributes/attributes.routes.js @@ -1,6 +1,6 @@ -import SettingsContent from '../Wrapper'; -import AttributesHome from './Index'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const AttributesHome = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js b/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js index f1fb18ec1..00c1fda74 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js @@ -1,7 +1,8 @@ -import SettingsContent from '../Wrapper'; -import AuditLogsHome from './Index'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const AuditLogsHome = () => import('./Index.vue'); + export default { routes: [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/automation.routes.js b/app/javascript/dashboard/routes/dashboard/settings/automation/automation.routes.js index dbb204261..3a5d6c887 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/automation/automation.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/automation/automation.routes.js @@ -1,6 +1,6 @@ -import SettingsContent from '../Wrapper'; -import Automation from './Index'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Automation = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/billing/billing.routes.js b/app/javascript/dashboard/routes/dashboard/settings/billing/billing.routes.js index db28fb844..7a37f320a 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/billing/billing.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/billing/billing.routes.js @@ -1,6 +1,6 @@ -import SettingsContent from '../Wrapper'; -import Index from './Index.vue'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Index = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js b/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js index 4de15b068..b25150dee 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js @@ -1,6 +1,6 @@ -import Index from './Index'; -import SettingsContent from '../Wrapper'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Index = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js b/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js index 3b68de1db..73b370e2a 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js @@ -1,7 +1,8 @@ -import SettingsContent from '../Wrapper'; -import CannedHome from './Index'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const CannedHome = () => import('./Index.vue'); + export default { routes: [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/inbox.routes.js b/app/javascript/dashboard/routes/dashboard/settings/inbox/inbox.routes.js index 9423a915d..4e4c14c00 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/inbox.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/inbox.routes.js @@ -1,13 +1,14 @@ /* eslint arrow-body-style: 0 */ -import SettingsContent from '../Wrapper'; -import Settings from './Settings'; -import InboxHome from './Index'; -import InboxChannel from './InboxChannels'; -import ChannelList from './ChannelList'; -import channelFactory from './channel-factory'; -import AddAgents from './AddAgents'; -import FinishSetup from './FinishSetup'; import { frontendURL } from '../../../../helper/URLHelper'; +import channelFactory from './channel-factory'; + +const SettingsContent = () => import('../Wrapper.vue'); +const InboxHome = () => import('./Index.vue'); +const Settings = () => import('./Settings.vue'); +const InboxChannel = () => import('./InboxChannels.vue'); +const ChannelList = () => import('./ChannelList.vue'); +const AddAgents = () => import('./AddAgents.vue'); +const FinishSetup = () => import('./FinishSetup.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrationapps/integrations.routes.js b/app/javascript/dashboard/routes/dashboard/settings/integrationapps/integrations.routes.js index 67f5a5f07..a0f8477b2 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/integrationapps/integrations.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/integrationapps/integrations.routes.js @@ -1,7 +1,7 @@ -import Index from './Index'; -import SettingsContent from '../Wrapper'; -import IntegrationHooks from './IntegrationHooks'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const IntegrationHooks = () => import('./IntegrationHooks.vue'); +const Index = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js b/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js index e2be34338..542cf591e 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js @@ -1,11 +1,12 @@ -import Index from './Index'; -import SettingsContent from '../Wrapper'; -import Webhook from './Webhooks/Index'; -import DashboardApps from './DashboardApps/Index'; -import ShowIntegration from './ShowIntegration'; -import Slack from './Slack'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Webhook = () => import('./Webhooks/Index.vue'); +const DashboardApps = () => import('./DashboardApps/Index.vue'); +const ShowIntegration = () => import('./ShowIntegration.vue'); +const Slack = () => import('./Slack.vue'); +const Index = () => import('./Index.vue'); + export default { routes: [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js b/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js index 6424aaea4..4fc514da6 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js @@ -1,7 +1,8 @@ -import SettingsContent from '../Wrapper'; -import Index from './Index'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Index = () => import('./Index.vue'); + export default { routes: [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js b/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js index 23306a8bf..58a26819f 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js @@ -1,8 +1,9 @@ -import SettingsContent from '../Wrapper'; -import Macros from './Index'; -const MacroEditor = () => import('./MacroEditor'); import { frontendURL } from 'dashboard/helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Macros = () => import('./Index.vue'); +const MacroEditor = () => import('./MacroEditor.vue'); + export default { routes: [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/profile.routes.js b/app/javascript/dashboard/routes/dashboard/settings/profile/profile.routes.js index 72fc340e0..634d15491 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/profile/profile.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/profile/profile.routes.js @@ -1,7 +1,8 @@ -import SettingsContent from '../Wrapper'; -import Index from './Index.vue'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Index = () => import('./Index.vue'); + export default { routes: [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js b/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js index 4d4814845..31429e9c2 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js @@ -1,13 +1,14 @@ -import Index from './Index'; -import AgentReports from './AgentReports'; -import LabelReports from './LabelReports'; -import InboxReports from './InboxReports'; -import TeamReports from './TeamReports'; -import CsatResponses from './CsatResponses'; -import LiveReports from './LiveReports'; -import SettingsContent from '../Wrapper'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Index = () => import('./Index.vue'); +const AgentReports = () => import('./AgentReports.vue'); +const LabelReports = () => import('./LabelReports.vue'); +const InboxReports = () => import('./InboxReports.vue'); +const TeamReports = () => import('./TeamReports.vue'); +const CsatResponses = () => import('./CsatResponses.vue'); +const LiveReports = () => import('./LiveReports.vue'); + export default { routes: [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/teams/teams.routes.js b/app/javascript/dashboard/routes/dashboard/settings/teams/teams.routes.js index a04c60882..4228e72e7 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/teams/teams.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/teams/teams.routes.js @@ -1,15 +1,16 @@ /* eslint arrow-body-style: 0 */ -import SettingsContent from '../Wrapper'; -import TeamsHome from './Index'; -import CreateStepWrap from './Create/Index'; -import EditStepWrap from './Edit/Index'; -import CreateTeam from './Create/CreateTeam'; -import EditTeam from './Edit/EditTeam'; -import AddAgents from './Create/AddAgents'; -import EditAgents from './Edit/EditAgents'; -import FinishSetup from './FinishSetup'; import { frontendURL } from '../../../../helper/URLHelper'; +const CreateStepWrap = () => import('./Create/Index.vue'); +const EditStepWrap = () => import('./Edit/Index.vue'); +const CreateTeam = () => import('./Create/CreateTeam.vue'); +const EditTeam = () => import('./Edit/EditTeam.vue'); +const AddAgents = () => import('./Create/AddAgents.vue'); +const EditAgents = () => import('./Edit/EditAgents.vue'); +const FinishSetup = () => import('./FinishSetup.vue'); +const SettingsContent = () => import('../Wrapper.vue'); +const TeamsHome = () => import('./Index.vue'); + export default { routes: [ { From 96add30331da80810c9b4657e15843d7a5800d24 Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Wed, 22 Nov 2023 21:48:58 +0530 Subject: [PATCH 06/63] chore: Add analytics event for insert article feature (#8393) --- .../dashboard/components/widgets/conversation/ReplyBox.vue | 2 ++ app/javascript/dashboard/helper/AnalyticsHelper/events.js | 1 + 2 files changed, 3 insertions(+) diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index 5e1f34904..3712a08e6 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -624,6 +624,8 @@ export default { `${this.$t('CONVERSATION.REPLYBOX.INSERT_READ_MORE')} ${url}` ); } + + this.$track(CONVERSATION_EVENTS.INSERT_ARTICLE_LINK); }, toggleRichContentEditor() { this.updateUISettings({ diff --git a/app/javascript/dashboard/helper/AnalyticsHelper/events.js b/app/javascript/dashboard/helper/AnalyticsHelper/events.js index 53e0a82ca..30f82d554 100644 --- a/app/javascript/dashboard/helper/AnalyticsHelper/events.js +++ b/app/javascript/dashboard/helper/AnalyticsHelper/events.js @@ -9,6 +9,7 @@ export const CONVERSATION_EVENTS = Object.freeze({ SEARCH_CONVERSATION: 'Searched conversations', APPLY_FILTER: 'Applied filters in the conversation list', CHANGE_PRIORITY: 'Assigned priority to a conversation', + INSERT_ARTICLE_LINK: 'Inserted article into reply via article search', }); export const ACCOUNT_EVENTS = Object.freeze({ From 1904ec7df44446bdea04f1a67f92ff65baa468a3 Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Wed, 22 Nov 2023 09:39:41 -0800 Subject: [PATCH 07/63] fix: Update campaign routes to fix the rendering issue (#8400) --- .../dashboard/settings/campaigns/campaigns.routes.js | 4 ++-- .../routes/dashboard/settings/macros/MacroForm.vue | 11 +++++------ .../dashboard/settings/macros/MacroProperties.vue | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js b/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js index b25150dee..149593b7e 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js @@ -20,7 +20,7 @@ export default { path: 'ongoing', name: 'settings_account_campaigns', roles: ['administrator'], - component: { ...Index }, + component: Index, }, ], }, @@ -36,7 +36,7 @@ export default { path: 'one_off', name: 'one_off', roles: ['administrator'], - component: { ...Index }, + component: Index, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue index cc161bf4f..3cebed550 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue @@ -1,6 +1,8 @@ + <% end %> + + -
-
+
+
<% if !@is_plain_layout_enabled %> <%= render "public/api/v1/portals/header", portal: @portal %> <% end %> @@ -42,17 +70,53 @@ By default, it renders:
+ diff --git a/app/views/super_admin/application/_nav_item.html.erb b/app/views/super_admin/application/_nav_item.html.erb new file mode 100644 index 000000000..a6366d8b5 --- /dev/null +++ b/app/views/super_admin/application/_nav_item.html.erb @@ -0,0 +1,9 @@ +
  • + <% text_class_name = current_page?(url) ? 'text-woot-500 bg-slate-25' : 'text-slate-800' %> + <%= link_to(url, class: text_class_name + " -ml-1 focus:outline-none cursor-pointer flex items-center px-2 py-1.5 text-slate-800 cursor-pointer hover:text-woot-500 hover:bg-slate-25 rounded-lg") do %> + + <%= label %> + <% end %> +
  • diff --git a/app/views/super_admin/application/_navigation.html.erb b/app/views/super_admin/application/_navigation.html.erb index b85c02623..f8fb474d2 100644 --- a/app/views/super_admin/application/_navigation.html.erb +++ b/app/views/super_admin/application/_navigation.html.erb @@ -6,88 +6,57 @@ By default, the navigation contains navigation links for all resources in the admin dashboard, as defined by the routes in the `admin/` namespace %> - <%= javascript_pack_tag 'superadmin_pages' %> <%= stylesheet_pack_tag 'superadmin_pages' %> + <% sidebar_icons = { - accounts: 'ion ion-briefcase', - users: 'ion ion-person-stalker', - super_admins: 'ion ion-unlocked', - access_tokens: 'ion-key', - platform_apps: 'ion ion-social-buffer', - installation_configs: 'ion ion-settings', - agent_bots: 'ion ion-social-android', + accounts: 'icon-building-4-line', + users: 'icon-user-follow-line', + platform_apps: 'icon-apps-2-line', + agent_bots: 'icon-robot-line', } %> -
    diff --git a/app/views/super_admin/instance_statuses/show.html.erb b/app/views/super_admin/instance_statuses/show.html.erb index d0d23f2a1..5474bff6a 100644 --- a/app/views/super_admin/instance_statuses/show.html.erb +++ b/app/views/super_admin/instance_statuses/show.html.erb @@ -1,5 +1,5 @@ <% content_for(:title) do %> - Instance Health + Instance Status <% end %>