From 37b386fa3d5b92a8c8948761b6b3d0addc8b375c Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Thu, 14 Dec 2023 21:46:51 +0800 Subject: [PATCH] reply multiple action and csat trigger --- app/builders/csat_surveys/response_builder.rb | 19 +- .../conversations/reply_action_controller.rb | 38 +++ .../v1/accounts/csat_templates_controller.rb | 10 +- .../api/v1/accounts/inboxes_controller.rb | 2 +- app/javascript/dashboard/api/csatTemplates.js | 10 + app/javascript/dashboard/api/inbox/message.js | 11 + .../dashboard/api/inbox/reply_action.js | 25 ++ .../dashboard/api/specs/inbox/reply_action.js | 45 ++++ .../widgets/WootWriter/ReplyBottomPanel.vue | 15 +- .../WootWriter/ReplyToMultipleAction.vue | 245 ++++++++++++++++++ .../widgets/conversation/Message.vue | 4 - .../widgets/conversation/ReplyBox.vue | 11 + .../helpers/botMessageContentHelper.js | 3 + .../dashboard/i18n/locale/en/inboxMgmt.json | 1 + .../routes/dashboard/settings/csat/Index.vue | 63 ++++- .../dashboard/settings/inbox/Settings.vue | 26 ++ .../reports/components/CsatQuestionGroup.vue | 8 +- .../dashboard/store/modules/csatTemplates.js | 14 + .../dashboard/store/modules/replyAction.js | 56 ++++ .../dashboard/store/mutation-types.js | 4 +- .../components/CustomerSatisfaction.vue | 9 +- .../survey/api/specs/endPoints.spec.js | 1 + .../store/modules/conversation/getters.js | 3 + .../store/modules/conversation/index.js | 1 + .../store/modules/conversation/mutations.js | 4 + app/models/account.rb | 3 +- app/models/csat_survey_response.rb | 2 +- app/models/csat_template_question.rb | 13 +- app/models/custom_attribute_definition.rb | 2 +- app/models/inbox.rb | 1 + app/models/message_csat_template_question.rb | 2 + .../_csat_survey_response.json.jbuilder | 4 +- app/views/api/v1/models/_inbox.json.jbuilder | 1 + .../public/api/v1/models/_inbox.json.jbuilder | 1 + config/routes.rb | 3 + ...09085733_add_inbox_default_reply_action.rb | 6 + db/schema.rb | 12 +- 37 files changed, 649 insertions(+), 29 deletions(-) create mode 100644 app/controllers/api/v1/accounts/conversations/reply_action_controller.rb create mode 100644 app/javascript/dashboard/api/inbox/reply_action.js create mode 100644 app/javascript/dashboard/api/specs/inbox/reply_action.js create mode 100644 app/javascript/dashboard/components/widgets/WootWriter/ReplyToMultipleAction.vue create mode 100644 app/javascript/dashboard/store/modules/replyAction.js create mode 100644 db/migrate/20231209085733_add_inbox_default_reply_action.rb diff --git a/app/builders/csat_surveys/response_builder.rb b/app/builders/csat_surveys/response_builder.rb index b5f6707fa6afe..f9f8ed53528d7 100644 --- a/app/builders/csat_surveys/response_builder.rb +++ b/app/builders/csat_surveys/response_builder.rb @@ -18,12 +18,27 @@ def perform def process_csat_response(conversation, rating, feedback_message) csat_survey_response = message.csat_survey_response || CsatSurveyResponse.new( message_id: message.id, account_id: message.account_id, conversation_id: message.conversation_id, - contact_id: conversation.contact_id, assigned_agent: conversation.assignee, - csat_template_id: message.csat_template_question&.csat_template_id, csat_template_question_id: message.csat_template_question&.id + contact_id: conversation.contact_id, assigned_agent: conversation.assignee ) + + update_message_content_attributes + csat_survey_response.csat_template_id = csat_template_question.csat_template_id + csat_survey_response.csat_template_question_id = csat_template_question.id csat_survey_response.rating = rating csat_survey_response.feedback_message = feedback_message csat_survey_response.save! csat_survey_response end + + def csat_template_question + @csat_template_question ||= (message.csat_template_question || CsatTemplateQuestion.load_by_content(message.content)) + end + + def update_message_content_attributes + return unless (attrs = message.content_attributes.dig(:submitted_values)).present? + + attrs['csat_template_question_id'] = csat_template_question&.id + message.content_attributes['submitted_values'] = attrs + message.save + end end diff --git a/app/controllers/api/v1/accounts/conversations/reply_action_controller.rb b/app/controllers/api/v1/accounts/conversations/reply_action_controller.rb new file mode 100644 index 0000000000000..a023e0efacc1f --- /dev/null +++ b/app/controllers/api/v1/accounts/conversations/reply_action_controller.rb @@ -0,0 +1,38 @@ +class Api::V1::Accounts::Conversations::ReplyActionController < Api::V1::Accounts::Conversations::BaseController + # def show + # render json: { action_type: 'default' } and return unless Redis::Alfred.exists?(action_redis_key) + + # action_type = Redis::Alfred.get(action_redis_key) + # render json: { action_type: action_type } + # end + + def show + puts ' ---------------- default action show -------------' + render json: { action_type: 'default_reply_action'} + end + + # def update + # # Redis::Alfred.set(action_redis_key, action_type_params) + # puts ' ---------------- default action updated -------------' + # head :ok + # end + + # def destroy + # Redis::Alfred.delete(action_redis_key) + # head :ok + # end + + # private + + # def action_redis_key + # format(Redis::Alfred::CONVERSATION_DRAFT_MESSAGE, conversation_id: conversation_id, account_id: current_account.id) + # end + + # def conversation_id + # params[:conversation_id] + # end + + # def action_type_params + # params.dig(:reply_action, :type) || '' + # end +end diff --git a/app/controllers/api/v1/accounts/csat_templates_controller.rb b/app/controllers/api/v1/accounts/csat_templates_controller.rb index f15917fed08a1..61c064c0864ab 100644 --- a/app/controllers/api/v1/accounts/csat_templates_controller.rb +++ b/app/controllers/api/v1/accounts/csat_templates_controller.rb @@ -23,6 +23,14 @@ def update # rubocop:enable Rails/SkipsModelValidations end + def update_csat_trigger + render json: { status: Current.account.update(csat_trigger: params[:csat_trigger]) } + end + + def csat_trigger + render json: { csat_trigger: Current.account.csat_trigger } + end + def destroy @template.destroy render json: { success: @template.destroyed? } @@ -33,7 +41,7 @@ def setting_status end def toggle_setting - Current.account.update(csat_template_enabled: params[:status]) + render json: { success: true } end def inboxes diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb index 011faaf280085..ed1986b033351 100644 --- a/app/controllers/api/v1/accounts/inboxes_controller.rb +++ b/app/controllers/api/v1/accounts/inboxes_controller.rb @@ -124,7 +124,7 @@ def update_channel_feature_flags 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, :portal_id, :sender_name_type, :business_name] + :lock_to_single_conversation, :portal_id, :sender_name_type, :business_name, :default_reply_action] end def permitted_params(channel_attributes = []) diff --git a/app/javascript/dashboard/api/csatTemplates.js b/app/javascript/dashboard/api/csatTemplates.js index de8e48aba68c0..5e5edde0b4e59 100644 --- a/app/javascript/dashboard/api/csatTemplates.js +++ b/app/javascript/dashboard/api/csatTemplates.js @@ -34,6 +34,16 @@ class CsatTemplatesAPI extends ApiClient { return axios.get(`${this.url}/setting_status`); } + getCsatTrigger() { + return axios.get(`${this.url}/csat_trigger`); + } + + updateCsatTrigger(csat_trigger) { + return axios.patch(`${this.url}/update_csat_trigger`, { + csat_trigger: csat_trigger + }); + } + toggleSetting(status) { return axios.patch(`${this.url}/toggle_setting`, { status: status, diff --git a/app/javascript/dashboard/api/inbox/message.js b/app/javascript/dashboard/api/inbox/message.js index 6cb885e3dc986..1798928ada14f 100644 --- a/app/javascript/dashboard/api/inbox/message.js +++ b/app/javascript/dashboard/api/inbox/message.js @@ -12,6 +12,7 @@ export const buildCreatePayload = ({ bccEmails = '', toEmails = '', templateParams, + contentType, }) => { let payload; if (files && files.length !== 0) { @@ -33,6 +34,9 @@ export const buildCreatePayload = ({ if (contentAttributes) { payload.append('content_attributes', JSON.stringify(contentAttributes)); } + if (contentType) { + payload.append('content_type', contentType); + } } else { payload = { content: message, @@ -44,7 +48,12 @@ export const buildCreatePayload = ({ to_emails: toEmails, template_params: templateParams, }; + + if (contentType) { + payload.content_type = contentType; + } } + return payload; }; @@ -64,6 +73,7 @@ class MessageApi extends ApiClient { bccEmails = '', toEmails = '', templateParams, + contentType, }) { return axios({ method: 'post', @@ -78,6 +88,7 @@ class MessageApi extends ApiClient { bccEmails, toEmails, templateParams, + contentType, }), }); } diff --git a/app/javascript/dashboard/api/inbox/reply_action.js b/app/javascript/dashboard/api/inbox/reply_action.js new file mode 100644 index 0000000000000..811c488b16982 --- /dev/null +++ b/app/javascript/dashboard/api/inbox/reply_action.js @@ -0,0 +1,25 @@ +/* eslint no-console: 0 */ +/* global axios */ +import ApiClient from '../ApiClient'; + +class ReplyActionApi extends ApiClient { + constructor() { + super('conversations', { accountScoped: true }); + } + + get(conversationId) { + return axios.get(`${this.url}/${conversationId}/reply_action`); + } + + update({ conversationId, message }) { + return axios.put(`${this.url}/${conversationId}/reply_action`, { + message, + }); + } + + delete(conversationId) { + return axios.delete(`${this.url}/${conversationId}/reply_action`); + } +} + +export default new ReplyActionApi(); diff --git a/app/javascript/dashboard/api/specs/inbox/reply_action.js b/app/javascript/dashboard/api/specs/inbox/reply_action.js new file mode 100644 index 0000000000000..38721b1f90467 --- /dev/null +++ b/app/javascript/dashboard/api/specs/inbox/reply_action.js @@ -0,0 +1,45 @@ +import replyActionApi from '../../inbox/conversation'; +import ApiClient from '../../ApiClient'; +import describeWithAPIMock from '../apiSpecHelper'; + +describe('#ReplyActionApi', () => { + it('creates correct instance', () => { + expect(replyActionApi).toBeInstanceOf(ApiClient); + expect(replyActionApi).toHaveProperty('get'); + expect(replyActionApi).toHaveProperty('update'); + expect(replyActionApi).toHaveProperty('delete'); + }); + + describeWithAPIMock('API calls', context => { + it('#get', () => { + replyActionApi.get({ + conversationId: 2, + }); + expect(context.axiosMock.get).toHaveBeenCalledWith( + `/api/v1/conversations/2/reply_action` + ); + }); + + it('#update', () => { + replyActionApi.update({ + conversationId: 45, + message: 'Hello', + }); + expect(context.axiosMock.post).toHaveBeenCalledWith( + '/api/v1/conversations/45/reply_action', + { + message: 'Hello', + } + ); + }); + + it('#delete', () => { + replyActionApi.delete({ + conversationId: 12, + }); + expect(context.axiosMock.delete).toHaveBeenCalledWith( + `/api/v1/conversations/12/reply_action` + ); + }); + }); +}); diff --git a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue index 8477accc22b62..4494698ca185e 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue @@ -111,6 +111,14 @@
+ {}, }, + onSendAsSurvey: { + type: Function, + default: () => {}, + }, sendButtonText: { type: String, default: '', diff --git a/app/javascript/dashboard/components/widgets/WootWriter/ReplyToMultipleAction.vue b/app/javascript/dashboard/components/widgets/WootWriter/ReplyToMultipleAction.vue new file mode 100644 index 0000000000000..aec82d3c40f77 --- /dev/null +++ b/app/javascript/dashboard/components/widgets/WootWriter/ReplyToMultipleAction.vue @@ -0,0 +1,245 @@ + + + + \ No newline at end of file diff --git a/app/javascript/dashboard/components/widgets/conversation/Message.vue b/app/javascript/dashboard/components/widgets/conversation/Message.vue index d7c9742ae637d..a53d9c9319f72 100644 --- a/app/javascript/dashboard/components/widgets/conversation/Message.vue +++ b/app/javascript/dashboard/components/widgets/conversation/Message.vue @@ -254,10 +254,6 @@ export default { } ); - if (this.contentType === 'input_csat') { - return this.$t('CONVERSATION.CSAT_REPLY_MESSAGE') + botMessageContent; - } - return ( this.formatMessage( this.data.content, diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index 3e119c03d0824..f0e69a1d24507 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -110,6 +110,7 @@ :mode="replyType" :on-file-upload="onFileUpload" :on-send="onSendReply" + :on-send-as-survey="onSendAsSurvey" :recording-audio-duration-text="recordingAudioDurationText" :recording-audio-state="recordingAudioState" :send-button-text="replyButtonLabel" @@ -248,6 +249,7 @@ export default { showCannedMenu: false, showVariablesMenu: false, newConversationModalActive: false, + sendAsSurvey: false, }; }, computed: { @@ -811,6 +813,10 @@ export default { this.confirmOnSendReply(); } }, + async onSendAsSurvey(){ + this.sendAsSurvey = true; + this.onSendReply() + }, async sendMessage(messagePayload) { try { await this.$store.dispatch( @@ -821,6 +827,7 @@ export default { bus.$emit(BUS_EVENTS.MESSAGE_SENT); this.removeFromDraft(); this.sendMessageAnalyticsData(messagePayload.private); + this.sendAsSurvey = false; } catch (error) { const errorMessage = error?.response?.data?.error || this.$t('CONVERSATION.MESSAGE_ERROR'); @@ -1069,6 +1076,10 @@ export default { messagePayload.toEmails = this.toEmails; } + if (this.sendAsSurvey){ + messagePayload.contentType = 'input_csat' + } + return messagePayload; }, setCcEmails(value) { diff --git a/app/javascript/dashboard/components/widgets/conversation/helpers/botMessageContentHelper.js b/app/javascript/dashboard/components/widgets/conversation/helpers/botMessageContentHelper.js index 2f4521f72ac18..802d833199d3e 100644 --- a/app/javascript/dashboard/components/widgets/conversation/helpers/botMessageContentHelper.js +++ b/app/javascript/dashboard/components/widgets/conversation/helpers/botMessageContentHelper.js @@ -52,6 +52,9 @@ const generateCSATContent = ( ); messageContent += `
${ratingTitle}
`; messageContent += `

${ratingObject.emoji}

`; + } else { + messageContent += `
${ratingTitle}
`; + messageContent += `

No rating yet.

`; } if (feedback_message) { messageContent += `
${feedbackTitle}
`; diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json index 74eff203383a1..9cb30e978fa5a 100644 --- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json @@ -507,6 +507,7 @@ "FORWARD_EMAIL_SUB_TEXT": "Start forwarding your emails to the following email address.", "ALLOW_MESSAGES_AFTER_RESOLVED": "Allow messages after conversation resolved", "ALLOW_MESSAGES_AFTER_RESOLVED_SUB_TEXT": "Allow the end-users to send messages even after the conversation is resolved.", + "SET_DEFAULT_ACTION_REPLY": "Set default reply action", "WHATSAPP_SECTION_SUBHEADER": "This API Key is used for the integration with the WhatsApp APIs.", "WHATSAPP_SECTION_UPDATE_SUBHEADER": "Enter the updated key to be used for the integration with the WhatsApp APIs.", "WHATSAPP_SECTION_TITLE": "API Key", diff --git a/app/javascript/dashboard/routes/dashboard/settings/csat/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/csat/Index.vue index 5db3248818105..4fe2a846c1680 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/csat/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/csat/Index.vue @@ -6,7 +6,7 @@ {{ $t('CSAT_SETTINGS.CARD.HEADER') }} @@ -16,7 +16,29 @@


-
+
+ CSAT Triggers + +
+
+

+ Configurate the triggers for Customer Satisfaction (CSAT) +

+
+
+
+ CSAT Trigger Sub Option + +
+
+
{{ $t('CSAT_SETTINGS.TEMPLATE.TITLE') }}
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue index 7c92674a82575..a3744f03b1c87 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue @@ -180,6 +180,28 @@

+ +