mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	feat: Link help center portal to an Inbox (#6903)
This commit is contained in:
		| @@ -114,10 +114,13 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController | |||||||
|   def inbox_attributes |   def inbox_attributes | ||||||
|     [:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled, |     [:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled, | ||||||
|      :enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved, |      :enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved, | ||||||
|      :lock_to_single_conversation] |      :lock_to_single_conversation, :portal_id] | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def permitted_params(channel_attributes = []) |   def permitted_params(channel_attributes = []) | ||||||
|  |     # We will remove this line after fixing https://linear.app/chatwoot/issue/CW-1567/null-value-passed-as-null-string-to-backend | ||||||
|  |     params.each { |k, v| params[k] = params[k] == 'null' ? nil : v } | ||||||
|  |  | ||||||
|     params.permit( |     params.permit( | ||||||
|       *inbox_attributes, |       *inbox_attributes, | ||||||
|       channel: [:type, *channel_attributes] |       channel: [:type, *channel_attributes] | ||||||
|   | |||||||
| @@ -482,6 +482,13 @@ | |||||||
|       "WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.", |       "WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.", | ||||||
|       "UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings" |       "UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings" | ||||||
|     }, |     }, | ||||||
|  |     "HELP_CENTER": { | ||||||
|  |       "LABEL": "Help Center", | ||||||
|  |       "PLACEHOLDER": "Select Help Center", | ||||||
|  |       "SELECT_PLACEHOLDER": "Select Help Center", | ||||||
|  |       "REMOVE": "Remove Help Center", | ||||||
|  |       "SUB_TEXT": "Attach a Help Center with the inbox" | ||||||
|  |     }, | ||||||
|     "AUTO_ASSIGNMENT": { |     "AUTO_ASSIGNMENT": { | ||||||
|       "MAX_ASSIGNMENT_LIMIT": "Auto assignment limit", |       "MAX_ASSIGNMENT_LIMIT": "Auto assignment limit", | ||||||
|       "MAX_ASSIGNMENT_LIMIT_RANGE_ERROR": "Please enter a value greater than 0", |       "MAX_ASSIGNMENT_LIMIT_RANGE_ERROR": "Please enter a value greater than 0", | ||||||
|   | |||||||
| @@ -258,7 +258,22 @@ | |||||||
|             }} |             }} | ||||||
|           </p> |           </p> | ||||||
|         </label> |         </label> | ||||||
|  |         <div class="medium-9 settings-item settings-item"> | ||||||
|  |           <label> | ||||||
|  |             {{ $t('INBOX_MGMT.HELP_CENTER.LABEL') }} | ||||||
|  |           </label> | ||||||
|  |           <select v-model="selectedPortalSlug" class="filter__question"> | ||||||
|  |             <option value=""> | ||||||
|  |               {{ $t('INBOX_MGMT.HELP_CENTER.PLACEHOLDER') }} | ||||||
|  |             </option> | ||||||
|  |             <option v-for="p in portals" :key="p.slug" :value="p.slug"> | ||||||
|  |               {{ p.name }} | ||||||
|  |             </option> | ||||||
|  |           </select> | ||||||
|  |           <p class="help-text"> | ||||||
|  |             {{ $t('INBOX_MGMT.HELP_CENTER.SUB_TEXT') }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|         <label |         <label | ||||||
|           v-if="canLocktoSingleConversation" |           v-if="canLocktoSingleConversation" | ||||||
|           class="medium-9 columns settings-item" |           class="medium-9 columns settings-item" | ||||||
| @@ -425,6 +440,7 @@ export default { | |||||||
|       selectedFeatureFlags: [], |       selectedFeatureFlags: [], | ||||||
|       replyTime: '', |       replyTime: '', | ||||||
|       selectedTabIndex: 0, |       selectedTabIndex: 0, | ||||||
|  |       selectedPortalSlug: '', | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
| @@ -432,6 +448,7 @@ export default { | |||||||
|       accountId: 'getCurrentAccountId', |       accountId: 'getCurrentAccountId', | ||||||
|       isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount', |       isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount', | ||||||
|       uiFlags: 'inboxes/getUIFlags', |       uiFlags: 'inboxes/getUIFlags', | ||||||
|  |       portals: 'portals/allPortals', | ||||||
|     }), |     }), | ||||||
|     selectedTabKey() { |     selectedTabKey() { | ||||||
|       return this.tabs[this.selectedTabIndex]?.key; |       return this.tabs[this.selectedTabIndex]?.key; | ||||||
| @@ -519,6 +536,7 @@ export default { | |||||||
|     inbox() { |     inbox() { | ||||||
|       return this.$store.getters['inboxes/getInbox'](this.currentInboxId); |       return this.$store.getters['inboxes/getInbox'](this.currentInboxId); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     inboxName() { |     inboxName() { | ||||||
|       if (this.isATwilioSMSChannel || this.isATwilioWhatsAppChannel) { |       if (this.isATwilioSMSChannel || this.isATwilioWhatsAppChannel) { | ||||||
|         return `${this.inbox.name} (${this.inbox.messaging_service_sid || |         return `${this.inbox.name} (${this.inbox.messaging_service_sid || | ||||||
| @@ -566,8 +584,12 @@ export default { | |||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     this.fetchInboxSettings(); |     this.fetchInboxSettings(); | ||||||
|  |     this.fetchPortals(); | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|  |     fetchPortals() { | ||||||
|  |       this.$store.dispatch('portals/index'); | ||||||
|  |     }, | ||||||
|     handleFeatureFlag(e) { |     handleFeatureFlag(e) { | ||||||
|       this.selectedFeatureFlags = this.toggleInput( |       this.selectedFeatureFlags = this.toggleInput( | ||||||
|         this.selectedFeatureFlags, |         this.selectedFeatureFlags, | ||||||
| @@ -607,6 +629,9 @@ export default { | |||||||
|         this.selectedFeatureFlags = this.inbox.selected_feature_flags || []; |         this.selectedFeatureFlags = this.inbox.selected_feature_flags || []; | ||||||
|         this.replyTime = this.inbox.reply_time; |         this.replyTime = this.inbox.reply_time; | ||||||
|         this.locktoSingleConversation = this.inbox.lock_to_single_conversation; |         this.locktoSingleConversation = this.inbox.lock_to_single_conversation; | ||||||
|  |         this.selectedPortalSlug = this.inbox.help_center | ||||||
|  |           ? this.inbox.help_center.slug | ||||||
|  |           : ''; | ||||||
|       }); |       }); | ||||||
|     }, |     }, | ||||||
|     async updateInbox() { |     async updateInbox() { | ||||||
| @@ -619,6 +644,11 @@ export default { | |||||||
|           allow_messages_after_resolved: this.allowMessagesAfterResolved, |           allow_messages_after_resolved: this.allowMessagesAfterResolved, | ||||||
|           greeting_enabled: this.greetingEnabled, |           greeting_enabled: this.greetingEnabled, | ||||||
|           greeting_message: this.greetingMessage || '', |           greeting_message: this.greetingMessage || '', | ||||||
|  |           portal_id: this.selectedPortalSlug | ||||||
|  |             ? this.portals.find( | ||||||
|  |                 portal => portal.slug === this.selectedPortalSlug | ||||||
|  |               ).id | ||||||
|  |             : null, | ||||||
|           lock_to_single_conversation: this.locktoSingleConversation, |           lock_to_single_conversation: this.locktoSingleConversation, | ||||||
|           channel: { |           channel: { | ||||||
|             widget_color: this.inbox.widget_color, |             widget_color: this.inbox.widget_color, | ||||||
|   | |||||||
| @@ -23,11 +23,17 @@ | |||||||
| #  updated_at                    :datetime         not null | #  updated_at                    :datetime         not null | ||||||
| #  account_id                    :integer          not null | #  account_id                    :integer          not null | ||||||
| #  channel_id                    :integer          not null | #  channel_id                    :integer          not null | ||||||
|  | #  portal_id                     :bigint | ||||||
| # | # | ||||||
| # Indexes | # Indexes | ||||||
| # | # | ||||||
| #  index_inboxes_on_account_id                   (account_id) | #  index_inboxes_on_account_id                   (account_id) | ||||||
| #  index_inboxes_on_channel_id_and_channel_type  (channel_id,channel_type) | #  index_inboxes_on_channel_id_and_channel_type  (channel_id,channel_type) | ||||||
|  | #  index_inboxes_on_portal_id                    (portal_id) | ||||||
|  | # | ||||||
|  | # Foreign Keys | ||||||
|  | # | ||||||
|  | #  fk_rails_...  (portal_id => portals.id) | ||||||
| # | # | ||||||
|  |  | ||||||
| class Inbox < ApplicationRecord | class Inbox < ApplicationRecord | ||||||
| @@ -45,6 +51,7 @@ class Inbox < ApplicationRecord | |||||||
|   validate :ensure_valid_max_assignment_limit |   validate :ensure_valid_max_assignment_limit | ||||||
|  |  | ||||||
|   belongs_to :account |   belongs_to :account | ||||||
|  |   belongs_to :portal, optional: true | ||||||
|  |  | ||||||
|   belongs_to :channel, polymorphic: true, dependent: :destroy |   belongs_to :channel, polymorphic: true, dependent: :destroy | ||||||
|  |  | ||||||
|   | |||||||
| @@ -37,6 +37,7 @@ class Portal < ApplicationRecord | |||||||
|            dependent: :nullify, |            dependent: :nullify, | ||||||
|            source: :user |            source: :user | ||||||
|   has_one_attached :logo |   has_one_attached :logo | ||||||
|  |   has_many :inboxes, dependent: :nullify | ||||||
|  |  | ||||||
|   before_validation -> { normalize_empty_string_to_nil(%i[custom_domain homepage_link]) } |   before_validation -> { normalize_empty_string_to_nil(%i[custom_domain homepage_link]) } | ||||||
|   validates :account_id, presence: true |   validates :account_id, presence: true | ||||||
|   | |||||||
| @@ -17,6 +17,13 @@ json.callback_webhook_url resource.callback_webhook_url | |||||||
| json.allow_messages_after_resolved resource.allow_messages_after_resolved | json.allow_messages_after_resolved resource.allow_messages_after_resolved | ||||||
| json.lock_to_single_conversation resource.lock_to_single_conversation | json.lock_to_single_conversation resource.lock_to_single_conversation | ||||||
|  |  | ||||||
|  | if resource.portal.present? | ||||||
|  |   json.help_center do | ||||||
|  |     json.name resource.portal.name | ||||||
|  |     json.slug resource.portal.slug | ||||||
|  |   end | ||||||
|  | end | ||||||
|  |  | ||||||
| ## Channel specific settings | ## Channel specific settings | ||||||
| ## TODO : Clean up and move the attributes into channel sub section | ## TODO : Clean up and move the attributes into channel sub section | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								db/migrate/20230413085302_add_portal_id_to_inbox.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								db/migrate/20230413085302_add_portal_id_to_inbox.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | class AddPortalIdToInbox < ActiveRecord::Migration[6.1] | ||||||
|  |   def change | ||||||
|  |     add_reference :inboxes, :portal, foreign_key: true | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -579,8 +579,10 @@ ActiveRecord::Schema.define(version: 2023_04_18_100944) do | |||||||
|     t.boolean "allow_messages_after_resolved", default: true |     t.boolean "allow_messages_after_resolved", default: true | ||||||
|     t.jsonb "auto_assignment_config", default: {} |     t.jsonb "auto_assignment_config", default: {} | ||||||
|     t.boolean "lock_to_single_conversation", default: false, null: false |     t.boolean "lock_to_single_conversation", default: false, null: false | ||||||
|  |     t.bigint "portal_id" | ||||||
|     t.index ["account_id"], name: "index_inboxes_on_account_id" |     t.index ["account_id"], name: "index_inboxes_on_account_id" | ||||||
|     t.index ["channel_id", "channel_type"], name: "index_inboxes_on_channel_id_and_channel_type" |     t.index ["channel_id", "channel_type"], name: "index_inboxes_on_channel_id_and_channel_type" | ||||||
|  |     t.index ["portal_id"], name: "index_inboxes_on_portal_id" | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   create_table "installation_configs", force: :cascade do |t| |   create_table "installation_configs", force: :cascade do |t| | ||||||
| @@ -918,6 +920,7 @@ ActiveRecord::Schema.define(version: 2023_04_18_100944) do | |||||||
|  |  | ||||||
|   add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" |   add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" | ||||||
|   add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" |   add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" | ||||||
|  |   add_foreign_key "inboxes", "portals" | ||||||
|   create_trigger("accounts_after_insert_row_tr", :generated => true, :compatibility => 1). |   create_trigger("accounts_after_insert_row_tr", :generated => true, :compatibility => 1). | ||||||
|       on("accounts"). |       on("accounts"). | ||||||
|       after(:insert). |       after(:insert). | ||||||
|   | |||||||
| @@ -429,7 +429,8 @@ RSpec.describe 'Inboxes API', type: :request do | |||||||
|  |  | ||||||
|     context 'when it is an authenticated user' do |     context 'when it is an authenticated user' do | ||||||
|       let(:admin) { create(:user, account: account, role: :administrator) } |       let(:admin) { create(:user, account: account, role: :administrator) } | ||||||
|       let(:valid_params) { { name: 'new test inbox', enable_auto_assignment: false } } |       let!(:portal) { create(:portal, account_id: account.id) } | ||||||
|  |       let(:valid_params) { { name: 'new test inbox', enable_auto_assignment: false, portal_id: portal.id } } | ||||||
|  |  | ||||||
|       it 'will not update inbox for agent' do |       it 'will not update inbox for agent' do | ||||||
|         agent = create(:user, account: account, role: :agent) |         agent = create(:user, account: account, role: :agent) | ||||||
| @@ -450,6 +451,7 @@ RSpec.describe 'Inboxes API', type: :request do | |||||||
|  |  | ||||||
|         expect(response).to have_http_status(:success) |         expect(response).to have_http_status(:success) | ||||||
|         expect(inbox.reload.enable_auto_assignment).to be_falsey |         expect(inbox.reload.enable_auto_assignment).to be_falsey | ||||||
|  |         expect(inbox.reload.portal_id).to eq(portal.id) | ||||||
|         expect(JSON.parse(response.body)['name']).to eq 'new test inbox' |         expect(JSON.parse(response.body)['name']).to eq 'new test inbox' | ||||||
|       end |       end | ||||||
|  |  | ||||||
|   | |||||||
| @@ -197,11 +197,19 @@ RSpec.describe Inbox do | |||||||
|  |  | ||||||
|   describe '#update' do |   describe '#update' do | ||||||
|     let!(:inbox) { FactoryBot.create(:inbox) } |     let!(:inbox) { FactoryBot.create(:inbox) } | ||||||
|  |     let!(:portal) { FactoryBot.create(:portal) } | ||||||
|  |  | ||||||
|     before do |     before do | ||||||
|       allow(Rails.configuration.dispatcher).to receive(:dispatch) |       allow(Rails.configuration.dispatcher).to receive(:dispatch) | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  |     it 'set portal id in inbox' do | ||||||
|  |       inbox.portal_id = portal.id | ||||||
|  |       inbox.save | ||||||
|  |  | ||||||
|  |       expect(inbox.portal).to eq(portal) | ||||||
|  |     end | ||||||
|  |  | ||||||
|     it 'resets cache key if there is an update in the channel' do |     it 'resets cache key if there is an update in the channel' do | ||||||
|       channel = inbox.channel |       channel = inbox.channel | ||||||
|       channel.update(widget_color: '#fff') |       channel.update(widget_color: '#fff') | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ RSpec.describe Portal, type: :model do | |||||||
|     it { is_expected.to have_many(:articles) } |     it { is_expected.to have_many(:articles) } | ||||||
|     it { is_expected.to have_many(:portal_members) } |     it { is_expected.to have_many(:portal_members) } | ||||||
|     it { is_expected.to have_many(:members) } |     it { is_expected.to have_many(:members) } | ||||||
|  |     it { is_expected.to have_many(:inboxes) } | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   describe 'validations' do |   describe 'validations' do | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Tejaswini Chile
					Tejaswini Chile