mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-29 18:22:53 +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
|
||||
[: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,
|
||||
:lock_to_single_conversation]
|
||||
:lock_to_single_conversation, :portal_id]
|
||||
end
|
||||
|
||||
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(
|
||||
*inbox_attributes,
|
||||
channel: [:type, *channel_attributes]
|
||||
|
||||
@@ -482,6 +482,13 @@
|
||||
"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"
|
||||
},
|
||||
"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": {
|
||||
"MAX_ASSIGNMENT_LIMIT": "Auto assignment limit",
|
||||
"MAX_ASSIGNMENT_LIMIT_RANGE_ERROR": "Please enter a value greater than 0",
|
||||
|
||||
@@ -258,7 +258,22 @@
|
||||
}}
|
||||
</p>
|
||||
</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
|
||||
v-if="canLocktoSingleConversation"
|
||||
class="medium-9 columns settings-item"
|
||||
@@ -425,6 +440,7 @@ export default {
|
||||
selectedFeatureFlags: [],
|
||||
replyTime: '',
|
||||
selectedTabIndex: 0,
|
||||
selectedPortalSlug: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -432,6 +448,7 @@ export default {
|
||||
accountId: 'getCurrentAccountId',
|
||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||
uiFlags: 'inboxes/getUIFlags',
|
||||
portals: 'portals/allPortals',
|
||||
}),
|
||||
selectedTabKey() {
|
||||
return this.tabs[this.selectedTabIndex]?.key;
|
||||
@@ -519,6 +536,7 @@ export default {
|
||||
inbox() {
|
||||
return this.$store.getters['inboxes/getInbox'](this.currentInboxId);
|
||||
},
|
||||
|
||||
inboxName() {
|
||||
if (this.isATwilioSMSChannel || this.isATwilioWhatsAppChannel) {
|
||||
return `${this.inbox.name} (${this.inbox.messaging_service_sid ||
|
||||
@@ -566,8 +584,12 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.fetchInboxSettings();
|
||||
this.fetchPortals();
|
||||
},
|
||||
methods: {
|
||||
fetchPortals() {
|
||||
this.$store.dispatch('portals/index');
|
||||
},
|
||||
handleFeatureFlag(e) {
|
||||
this.selectedFeatureFlags = this.toggleInput(
|
||||
this.selectedFeatureFlags,
|
||||
@@ -607,6 +629,9 @@ export default {
|
||||
this.selectedFeatureFlags = this.inbox.selected_feature_flags || [];
|
||||
this.replyTime = this.inbox.reply_time;
|
||||
this.locktoSingleConversation = this.inbox.lock_to_single_conversation;
|
||||
this.selectedPortalSlug = this.inbox.help_center
|
||||
? this.inbox.help_center.slug
|
||||
: '';
|
||||
});
|
||||
},
|
||||
async updateInbox() {
|
||||
@@ -619,6 +644,11 @@ export default {
|
||||
allow_messages_after_resolved: this.allowMessagesAfterResolved,
|
||||
greeting_enabled: this.greetingEnabled,
|
||||
greeting_message: this.greetingMessage || '',
|
||||
portal_id: this.selectedPortalSlug
|
||||
? this.portals.find(
|
||||
portal => portal.slug === this.selectedPortalSlug
|
||||
).id
|
||||
: null,
|
||||
lock_to_single_conversation: this.locktoSingleConversation,
|
||||
channel: {
|
||||
widget_color: this.inbox.widget_color,
|
||||
|
||||
@@ -23,11 +23,17 @@
|
||||
# updated_at :datetime not null
|
||||
# account_id :integer not null
|
||||
# channel_id :integer not null
|
||||
# portal_id :bigint
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_inboxes_on_account_id (account_id)
|
||||
# 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
|
||||
@@ -45,6 +51,7 @@ class Inbox < ApplicationRecord
|
||||
validate :ensure_valid_max_assignment_limit
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :portal, optional: true
|
||||
|
||||
belongs_to :channel, polymorphic: true, dependent: :destroy
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class Portal < ApplicationRecord
|
||||
dependent: :nullify,
|
||||
source: :user
|
||||
has_one_attached :logo
|
||||
has_many :inboxes, dependent: :nullify
|
||||
|
||||
before_validation -> { normalize_empty_string_to_nil(%i[custom_domain homepage_link]) }
|
||||
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.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
|
||||
## 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.jsonb "auto_assignment_config", default: {}
|
||||
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 ["channel_id", "channel_type"], name: "index_inboxes_on_channel_id_and_channel_type"
|
||||
t.index ["portal_id"], name: "index_inboxes_on_portal_id"
|
||||
end
|
||||
|
||||
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_variant_records", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "inboxes", "portals"
|
||||
create_trigger("accounts_after_insert_row_tr", :generated => true, :compatibility => 1).
|
||||
on("accounts").
|
||||
after(:insert).
|
||||
|
||||
@@ -429,7 +429,8 @@ RSpec.describe 'Inboxes API', type: :request do
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
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
|
||||
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(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'
|
||||
end
|
||||
|
||||
|
||||
@@ -197,11 +197,19 @@ RSpec.describe Inbox do
|
||||
|
||||
describe '#update' do
|
||||
let!(:inbox) { FactoryBot.create(:inbox) }
|
||||
let!(:portal) { FactoryBot.create(:portal) }
|
||||
|
||||
before do
|
||||
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
||||
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
|
||||
channel = inbox.channel
|
||||
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(:portal_members) }
|
||||
it { is_expected.to have_many(:members) }
|
||||
it { is_expected.to have_many(:inboxes) }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
|
||||
Reference in New Issue
Block a user