feat: Link help center portal to an Inbox (#6903)

This commit is contained in:
Tejaswini Chile
2023-04-24 12:49:52 +05:30
committed by GitHub
parent f825a22997
commit e3193dcabc
11 changed files with 77 additions and 3 deletions

View File

@@ -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]

View File

@@ -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",

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
class AddPortalIdToInbox < ActiveRecord::Migration[6.1]
def change
add_reference :inboxes, :portal, foreign_key: true
end
end

View File

@@ -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).

View File

@@ -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

View File

@@ -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')

View File

@@ -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