From b0950d6880664af2ba56994847f2b19b0ad0ec92 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Fri, 10 Apr 2020 16:42:37 +0530 Subject: [PATCH] Feature: Rich Message Types (#610) Co-authored-by: Pranav Raj S Co-authored-by: Nithin David Thomas --- .../messages/outgoing/normal_builder.rb | 8 +- .../v1/accounts/conversations_controller.rb | 18 +++ .../api/v1/widget/base_controller.rb | 4 +- .../api/v1/widget/events_controller.rb | 16 ++ .../api/v1/widget/messages_controller.rb | 12 +- .../concerns/access_token_auth_helper.rb | 2 +- app/javascript/dashboard/api/inbox/message.js | 2 +- app/javascript/dashboard/i18n/de.js | 4 + app/javascript/dashboard/i18n/en.js | 3 + app/javascript/packs/sdk.js | 5 +- app/javascript/sdk/IFrameHelper.js | 3 + .../shared/components/CardButton.vue | 13 +- app/javascript/shared/components/ChatCard.vue | 1 - app/javascript/shared/components/ChatForm.vue | 114 +++++++++++++ .../shared/components/ChatOption.vue | 4 +- .../shared/components/ChatOptions.vue | 15 +- .../shared/mixins/messageFormatterMixin.js | 7 + app/javascript/widget/App.vue | 2 + app/javascript/widget/api/endPoints.js | 4 +- app/javascript/widget/api/events.js | 7 + app/javascript/widget/api/message.js | 5 +- .../widget/assets/scss/_buttons.scss | 4 + .../widget/components/AgentMessage.vue | 151 +++++++++++------- .../widget/components/AgentMessageBubble.vue | 97 +++++++++-- .../widget/components/UserMessageBubble.vue | 4 +- .../widget/components/template/Article.vue | 62 +++++++ .../widget/components/template/EmailInput.vue | 5 +- app/javascript/widget/helpers/actionCable.js | 3 + app/javascript/widget/i18n/en.js | 3 + app/javascript/widget/store/index.js | 4 +- .../widget/store/modules/conversation.js | 5 +- app/javascript/widget/store/modules/events.js | 19 +++ .../widget/store/modules/message.js | 19 ++- app/listeners/agent_bot_listener.rb | 24 +++ app/listeners/webhook_listener.rb | 24 +++ app/models/channel/web_widget.rb | 3 +- .../concerns/content_attribute_validator.rb | 52 ++++++ app/models/contact_inbox.rb | 15 ++ app/models/message.rb | 22 ++- .../conversations/create.json.jbuilder | 1 + .../messages/create.json.jbuilder | 4 +- .../partials/_conversation.json.jbuilder | 1 + .../v1/widget/messages/update.json.jbuilder | 2 +- config/routes.rb | 5 +- db/schema.rb | 2 + docs/webhooks/add-webhooks-to-chatwoot.md | 71 +++++++- lib/events/types.rb | 2 + .../conversations/messages_controller_spec.rb | 42 ++++- .../api/v1/widget/events_controller_spec.rb | 37 +++++ .../api/v1/widget/messages_controller_spec.rb | 10 +- .../factories/bot_message/bot_message_card.rb | 22 +++ .../bot_message/bot_message_select.rb | 10 ++ .../request/conversation/create_message.yml | 8 +- swagger/definitions/resource/conversation.yml | 3 + swagger/definitions/resource/message.yml | 7 + .../{list.yml => index_or_create.yml} | 36 +++++ swagger/paths/index.yml | 5 +- swagger/swagger.json | 110 ++++++++++--- 58 files changed, 997 insertions(+), 146 deletions(-) create mode 100644 app/controllers/api/v1/widget/events_controller.rb create mode 100644 app/javascript/shared/components/ChatForm.vue create mode 100644 app/javascript/widget/api/events.js create mode 100644 app/javascript/widget/components/template/Article.vue create mode 100644 app/javascript/widget/store/modules/events.js create mode 100644 app/models/concerns/content_attribute_validator.rb create mode 100644 app/views/api/v1/accounts/conversations/create.json.jbuilder create mode 100644 spec/controllers/api/v1/widget/events_controller_spec.rb create mode 100644 spec/factories/bot_message/bot_message_card.rb create mode 100644 spec/factories/bot_message/bot_message_select.rb rename swagger/paths/conversation/{list.yml => index_or_create.yml} (51%) diff --git a/app/builders/messages/outgoing/normal_builder.rb b/app/builders/messages/outgoing/normal_builder.rb index 668022e64..c420b2a41 100644 --- a/app/builders/messages/outgoing/normal_builder.rb +++ b/app/builders/messages/outgoing/normal_builder.rb @@ -3,11 +3,13 @@ class Messages::Outgoing::NormalBuilder attr_reader :message def initialize(user, conversation, params) - @content = params[:message] + @content = params[:content] @private = params[:private] || false @conversation = conversation @user = user @fb_id = params[:fb_id] + @content_type = params[:content_type] + @items = params.to_unsafe_h&.dig(:content_attributes, :items) @attachment = params[:attachment] end @@ -34,7 +36,9 @@ class Messages::Outgoing::NormalBuilder content: @content, private: @private, user_id: @user&.id, - source_id: @fb_id + source_id: @fb_id, + content_type: @content_type, + items: @items } end end diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index 895091cca..3050c8ab7 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -1,5 +1,6 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController before_action :conversation, except: [:index] + before_action :contact_inbox, only: [:create] def index result = conversation_finder.perform @@ -7,6 +8,10 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController @conversations_count = result[:count] end + def create + @conversation = ::Conversation.create!(conversation_params) + end + def show; end def toggle_status @@ -29,6 +34,19 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController @conversation ||= current_account.conversations.find_by(display_id: params[:id]) end + def contact_inbox + @contact_inbox ||= ::ContactInbox.find_by!(source_id: params[:source_id]) + end + + def conversation_params + { + account_id: current_account.id, + inbox_id: @contact_inbox.inbox_id, + contact_id: @contact_inbox.contact_id, + contact_inbox_id: @contact_inbox.id + } + end + def conversation_finder @conversation_finder ||= ConversationFinder.new(current_user, params) end diff --git a/app/controllers/api/v1/widget/base_controller.rb b/app/controllers/api/v1/widget/base_controller.rb index eb19e2bdd..9ef4a04f2 100644 --- a/app/controllers/api/v1/widget/base_controller.rb +++ b/app/controllers/api/v1/widget/base_controller.rb @@ -2,9 +2,9 @@ class Api::V1::Widget::BaseController < ApplicationController private def conversation - @conversation ||= @contact_inbox.conversations.find_by( + @conversation ||= @contact_inbox.conversations.where( inbox_id: auth_token_params[:inbox_id] - ) + ).last end def auth_token_params diff --git a/app/controllers/api/v1/widget/events_controller.rb b/app/controllers/api/v1/widget/events_controller.rb new file mode 100644 index 000000000..faa44994c --- /dev/null +++ b/app/controllers/api/v1/widget/events_controller.rb @@ -0,0 +1,16 @@ +class Api::V1::Widget::EventsController < Api::V1::Widget::BaseController + include Events::Types + before_action :set_web_widget + before_action :set_contact + + def create + Rails.configuration.dispatcher.dispatch(permitted_params[:name], Time.zone.now, contact_inbox: @contact_inbox) + head :no_content + end + + private + + def permitted_params + params.permit(:name, :website_token) + end +end diff --git a/app/controllers/api/v1/widget/messages_controller.rb b/app/controllers/api/v1/widget/messages_controller.rb index 7d16f7641..7e0c446ea 100644 --- a/app/controllers/api/v1/widget/messages_controller.rb +++ b/app/controllers/api/v1/widget/messages_controller.rb @@ -15,8 +15,12 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController end def update - @message.update!(input_submitted_email: contact_email) - update_contact(contact_email) + if @message.content_type == 'input_email' + @message.update!(submitted_email: contact_email) + update_contact(contact_email) + else + @message.update!(message_update_params[:message]) + end rescue StandardError => e render json: { error: @contact.errors, message: e.message }.to_json, status: 500 end @@ -116,6 +120,10 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController contact_email.split('@')[0] end + def message_update_params + params.permit(message: [submitted_values: [:name, :title, :value]]) + end + def permitted_params params.permit(:id, :before, :website_token, contact: [:email], message: [:content, :referer_url, :timestamp]) end diff --git a/app/controllers/concerns/access_token_auth_helper.rb b/app/controllers/concerns/access_token_auth_helper.rb index c5173e422..e7af9e116 100644 --- a/app/controllers/concerns/access_token_auth_helper.rb +++ b/app/controllers/concerns/access_token_auth_helper.rb @@ -1,6 +1,6 @@ module AccessTokenAuthHelper BOT_ACCESSIBLE_ENDPOINTS = { - 'api/v1/accounts/conversations' => ['toggle_status'], + 'api/v1/accounts/conversations' => %w[toggle_status create], 'api/v1/accounts/conversations/messages' => ['create'] }.freeze diff --git a/app/javascript/dashboard/api/inbox/message.js b/app/javascript/dashboard/api/inbox/message.js index 579d97a10..4ceac84d0 100644 --- a/app/javascript/dashboard/api/inbox/message.js +++ b/app/javascript/dashboard/api/inbox/message.js @@ -9,7 +9,7 @@ class MessageApi extends ApiClient { create({ conversationId, message, private: isPrivate }) { return axios.post(`${this.url}/${conversationId}/messages`, { - message, + content: message, private: isPrivate, }); } diff --git a/app/javascript/dashboard/i18n/de.js b/app/javascript/dashboard/i18n/de.js index 55cb77a8c..6988a5d95 100644 --- a/app/javascript/dashboard/i18n/de.js +++ b/app/javascript/dashboard/i18n/de.js @@ -15,6 +15,10 @@ export default { DOWNLOAD: 'Herunterladen', UPLOADING: 'Hochladen...', }, + + FORM_BUBBLE: { + SUBMIT: 'Einreichen', + }, }, CONFIRM_EMAIL: 'Überprüfen...', SETTINGS: { diff --git a/app/javascript/dashboard/i18n/en.js b/app/javascript/dashboard/i18n/en.js index ae0e144d6..61029e9af 100644 --- a/app/javascript/dashboard/i18n/en.js +++ b/app/javascript/dashboard/i18n/en.js @@ -15,6 +15,9 @@ export default { DOWNLOAD: 'Download', UPLOADING: 'Uploading...', }, + FORM_BUBBLE: { + SUBMIT: 'Submit', + }, }, CONFIRM_EMAIL: 'Verifying...', SETTINGS: { diff --git a/app/javascript/packs/sdk.js b/app/javascript/packs/sdk.js index 459c2bc4b..5a9826a7c 100755 --- a/app/javascript/packs/sdk.js +++ b/app/javascript/packs/sdk.js @@ -1,6 +1,5 @@ import Cookies from 'js-cookie'; import { IFrameHelper } from '../sdk/IFrameHelper'; -import { onBubbleClick } from '../sdk/bubbleHelpers'; const runSDK = ({ baseUrl, websiteToken }) => { const chatwootSettings = window.chatwootSettings || {}; @@ -13,7 +12,7 @@ const runSDK = ({ baseUrl, websiteToken }) => { websiteToken, toggle() { - onBubbleClick(); + IFrameHelper.events.toggleBubble(); }, setUser(identifier, user) { @@ -39,7 +38,7 @@ const runSDK = ({ baseUrl, websiteToken }) => { reset() { if (window.$chatwoot.isOpen) { - onBubbleClick(); + IFrameHelper.events.toggleBubble(); } Cookies.remove('cw_conversation'); diff --git a/app/javascript/sdk/IFrameHelper.js b/app/javascript/sdk/IFrameHelper.js index ec55c731d..ed85d7121 100644 --- a/app/javascript/sdk/IFrameHelper.js +++ b/app/javascript/sdk/IFrameHelper.js @@ -88,6 +88,9 @@ export const IFrameHelper = { toggleBubble: () => { onBubbleClick(); + if (window.$chatwoot.isOpen) { + IFrameHelper.pushEvent('webwidget.triggered'); + } }, }, onLoad: ({ widget_color: widgetColor }) => { diff --git a/app/javascript/shared/components/CardButton.vue b/app/javascript/shared/components/CardButton.vue index eea0b1a5a..d6d116ade 100644 --- a/app/javascript/shared/components/CardButton.vue +++ b/app/javascript/shared/components/CardButton.vue @@ -4,6 +4,8 @@ :key="action.uri" class="action-button button" :href="action.uri" + target="_blank" + rel="noopener nofollow noreferrer" > {{ action.text }} @@ -44,11 +46,14 @@ export default { @import '~dashboard/assets/scss/mixins.scss'; .action-button { - width: 100%; - padding: 0; - max-height: 34px; - margin-top: $space-smaller; + align-items: center; border-radius: $space-micro; + display: flex; font-weight: $font-weight-medium; + justify-content: center; + margin-top: $space-smaller; + max-height: 34px; + padding: 0; + width: 100%; } diff --git a/app/javascript/shared/components/ChatCard.vue b/app/javascript/shared/components/ChatCard.vue index 8accb6d8c..4e856bb6b 100644 --- a/app/javascript/shared/components/ChatCard.vue +++ b/app/javascript/shared/components/ChatCard.vue @@ -52,7 +52,6 @@ export default { @import '~dashboard/assets/scss/mixins.scss'; .card-message { - @include border-normal; background: white; max-width: 220px; padding: $space-small; diff --git a/app/javascript/shared/components/ChatForm.vue b/app/javascript/shared/components/ChatForm.vue new file mode 100644 index 000000000..f694db407 --- /dev/null +++ b/app/javascript/shared/components/ChatForm.vue @@ -0,0 +1,114 @@ +