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 @@
+
+
+
+
+ Reply And Resolve
+
+
+
+ Reply as pending
+
+
+
+ Reply with CSAT
+
+
+
+
+
+
+
+ setSelectedAction('reply_and_resolve')"
+ >
+ Reply And Resolve
+
+
+
+ setSelectedAction('reply_as_pending')"
+ >
+ Reply as pending
+
+
+
+ setSelectedAction('reply_with_csat')"
+ >
+ Reply with CSAT
+
+
+
+
+
+
+
+
+
\ 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
+
+ Send CSAT with all replies
+ Include CSAT with 'Reply and Resolve'
+ Send CSAT when conversation is closed
+
+
+
+
+ Configurate the triggers for Customer Satisfaction (CSAT)
+
+
+
+
+ CSAT Trigger Sub Option
+
+ Send CSAT as part of the reply
+ Send CSAT separately
+
+
+
+
{{ $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 @@
+
+ {{ $t('INBOX_MGMT.SETTINGS_POPUP.SET_DEFAULT_ACTION_REPLY') }}
+
+
+ Reply And Resolve
+
+
+ Reply with CSAT
+
+
+ Reply as Pending
+
+
+
+ {{
+ $t(
+ 'INBOX_MGMT.SETTINGS_POPUP.ALLOW_MESSAGES_AFTER_RESOLVED_SUB_TEXT'
+ )
+ }}
+
+
+
{{ $t('INBOX_MGMT.SETTINGS_POPUP.ENABLE_EMAIL_COLLECT_BOX') }}
@@ -475,6 +497,7 @@ export default {
channelWelcomeTagline: '',
selectedFeatureFlags: [],
replyTime: '',
+ defaultReplyAction: '',
selectedTabIndex: 0,
selectedPortalSlug: '',
showBusinessNameInput: false,
@@ -668,6 +691,8 @@ export default {
this.channelWelcomeTagline = this.inbox.welcome_tagline;
this.selectedFeatureFlags = this.inbox.selected_feature_flags || [];
this.replyTime = this.inbox.reply_time;
+ console.log(this.inbox)
+ this.defaultReplyAction = this.inbox.default_reply_action;
this.locktoSingleConversation = this.inbox.lock_to_single_conversation;
this.selectedPortalSlug = this.inbox.help_center
? this.inbox.help_center.slug
@@ -681,6 +706,7 @@ export default {
name: this.selectedInboxName,
enable_email_collect: this.emailCollectEnabled,
csat_survey_enabled: this.csatSurveyEnabled,
+ default_reply_action: this.defaultReplyAction,
allow_messages_after_resolved: this.allowMessagesAfterResolved,
greeting_enabled: this.greetingEnabled,
greeting_message: this.greetingMessage || '',
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatQuestionGroup.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatQuestionGroup.vue
index d706e8febe75c..9235225441abd 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatQuestionGroup.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatQuestionGroup.vue
@@ -3,14 +3,14 @@
@@ -38,9 +38,9 @@ export default {
}),
},
methods: {
- responsesFromQuestion(id) {
+ responsesFromQuestion(content) {
return this.tableData().filter(
- response => id === response.csatQuestionId
+ response => content === response.csatQuestion
);
},
columns() {
diff --git a/app/javascript/dashboard/store/modules/csatTemplates.js b/app/javascript/dashboard/store/modules/csatTemplates.js
index 75a5b584e8299..d1f7a682a8d81 100644
--- a/app/javascript/dashboard/store/modules/csatTemplates.js
+++ b/app/javascript/dashboard/store/modules/csatTemplates.js
@@ -9,6 +9,7 @@ export const state = {
inboxes_for_select: [],
current_template_id: 0,
current_template: {},
+ csat_trigger: ''
};
export const getters = {
@@ -27,6 +28,9 @@ export const getters = {
getCurrentTemplate(_state) {
return _state.current_template;
},
+ getCsatTrigger(_state) {
+ return _state.csat_trigger;
+ }
};
export const actions = {
@@ -76,12 +80,22 @@ export const actions = {
commit(types.DELETE_CSAT_TEMPLATE, id);
}
},
+ getCsatTrigger: async function getCsatTrigger({ commit }){
+ const response = await CsatTemplatesAPI.getCsatTrigger();
+ commit('SET_CSAT_TRIGGER', response.data.csat_trigger);
+ },
+ updateCsatTrigger: async function updateCsatTrigger({ _ }, csat_trigger){
+ await CsatTemplatesAPI.updateCsatTrigger(csat_trigger);
+ }
};
export const mutations = {
[types.ENABLE_CSAT_TEMPLATES]($state, status) {
Vue.set($state, 'csat_template_enabled', status);
},
+ ['SET_CSAT_TRIGGER']($state, csat_trigger) {
+ Vue.set($state, 'csat_trigger', csat_trigger);
+ },
[types.SET_CSAT_INBOXES]($state, inboxes) {
Vue.set($state, 'inboxes_for_select', inboxes);
},
diff --git a/app/javascript/dashboard/store/modules/replyAction.js b/app/javascript/dashboard/store/modules/replyAction.js
new file mode 100644
index 0000000000000..fa5118611bf3e
--- /dev/null
+++ b/app/javascript/dashboard/store/modules/replyAction.js
@@ -0,0 +1,56 @@
+import Vue from 'vue';
+import types from '../mutation-types';
+import ReplyActionApi from '../../api/inbox/reply_action';
+
+import { LocalStorage } from 'shared/helpers/localStorage';
+import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
+
+const state = {
+ records: {},
+};
+
+export const getters = {
+ get: _state => key => {
+ return _state.records[key] || '';
+ },
+};
+
+export const actions = {
+ set: async ({ commit }, {key, conversationId, action_type}) => {
+ commit(types.SET_REPLY_ACTION_MODE, { key, action_type });
+ ReplyActionApi.update({ conversationId, action_type });
+ },
+ // set: async ({ commit }, { key, conversationId, action_type }) => {
+ // commit(types.SET_REPLY_ACTION_MODE, { key, action_type });
+ // ReplyActionApi.update({ conversationId, message });
+ // },
+ // delete: async ({ commit }, { key, conversationId }) => {
+ // commit(types.REMOVE_REPLY_ACTION_MODE, { key });
+ // ReplyActionApi.delete(conversationId);
+ // },
+ // get: async ({ commit }, { key, conversationId }) => {
+ // const response = await ReplyActionApi.get(conversationId);
+
+ // if (response && response.data.action_type) {
+ // const action_type = response.data.action_type;
+ // commit(types.SET_REPLY_ACTION_MODE, { key, action_type });
+ // }
+ // },
+};
+
+export const mutations = {
+ // [types.SET_REPLY_ACTION_MODE]($state, { key, action_type }) {
+ // Vue.set($state, key, action_type);
+ // },
+ // [types.REMOVE_REPLY_ACTION_MODE]($state, { key }) {
+ // // Vue.set($state, key, action_type);
+ // },
+};
+
+export default {
+ namespaced: true,
+ state,
+ getters,
+ actions,
+ mutations,
+};
diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js
index 9ce8a1ac286d4..daa18f20bbdd8 100644
--- a/app/javascript/dashboard/store/mutation-types.js
+++ b/app/javascript/dashboard/store/mutation-types.js
@@ -28,7 +28,9 @@ export default {
SET_DRAFT_MESSAGES: 'SET_DRAFT_MESSAGES',
REMOVE_DRAFT_MESSAGES: 'REMOVE_DRAFT_MESSAGES',
SET_REPLY_EDITOR_MODE: 'SET_REPLY_EDITOR_MODE',
-
+ SET_REPLY_ACTION_MODE: 'SET_REPLY_ACTION_MODE',
+ REMOVE_REPLY_ACTION_MODE: 'REMOVE_REPLY_ACTION_MODE',
+
SET_CURRENT_CHAT_WINDOW: 'SET_CURRENT_CHAT_WINDOW',
CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW',
CLEAR_ALL_MESSAGES: 'CLEAR_ALL_MESSAGES',
diff --git a/app/javascript/shared/components/CustomerSatisfaction.vue b/app/javascript/shared/components/CustomerSatisfaction.vue
index bc9be5bbbaf84..fd3da50515742 100644
--- a/app/javascript/shared/components/CustomerSatisfaction.vue
+++ b/app/javascript/shared/components/CustomerSatisfaction.vue
@@ -18,7 +18,7 @@
Answered
{{ answeredCsatCount }}
/
- {{ csatCount }}
+ {{ submittedCsatCount }}
@@ -97,6 +97,7 @@ export default {
selectedRating: null,
isUpdating: false,
feedback: '',
+ csatTemplateQuestionId: '',
};
},
computed: {
@@ -104,8 +105,10 @@ export default {
widgetColor: 'appConfig/getWidgetColor',
csatCount: 'conversation/getTotalCsat',
answeredCsatCount: 'conversation/getTotalAnsweredCsat',
+ submittedCsatCount: 'conversation/getTotalSubmittedCsat',
csatTemplateEnabled: 'conversation/getCsatTemplateStatus',
}),
+
isRatingSubmitted() {
return this.messageContentAttributes?.csat_survey_response?.rating;
},
@@ -131,12 +134,14 @@ export default {
async mounted() {
if (this.isRatingSubmitted) {
const {
- csat_survey_response: { rating, feedback_message },
+ csat_survey_response: { csat_template_question_id, rating, feedback_message },
} = this.messageContentAttributes;
this.selectedRating = rating;
this.feedback = feedback_message;
+ this.csatTemplateQuestionId = csat_template_question_id;
this.$store.commit('conversation/incrementAnsweredCsat');
}
+ this.$store.commit('conversation/incrementSubmittedCsat');
},
methods: {
diff --git a/app/javascript/survey/api/specs/endPoints.spec.js b/app/javascript/survey/api/specs/endPoints.spec.js
index 8633f677f17fd..ff56513aa79b2 100644
--- a/app/javascript/survey/api/specs/endPoints.spec.js
+++ b/app/javascript/survey/api/specs/endPoints.spec.js
@@ -19,6 +19,7 @@ describe('#updateSurvey', () => {
message: {
submitted_values: {
csat_survey_response: {
+ csat_template_question_id: 0,
rating: 4,
feedback_message: 'amazing',
},
diff --git a/app/javascript/widget/store/modules/conversation/getters.js b/app/javascript/widget/store/modules/conversation/getters.js
index 1650a7e765a0a..0d304981914c3 100644
--- a/app/javascript/widget/store/modules/conversation/getters.js
+++ b/app/javascript/widget/store/modules/conversation/getters.js
@@ -67,4 +67,7 @@ export const getters = {
getCsatTemplateStatus: _state => {
return _state.csatTemplateEnabled;
},
+ getTotalSubmittedCsat: _state => {
+ return _state.totalSubmittedCsat;
+ },
};
diff --git a/app/javascript/widget/store/modules/conversation/index.js b/app/javascript/widget/store/modules/conversation/index.js
index 63d4186da7743..15f8cfcb6cead 100755
--- a/app/javascript/widget/store/modules/conversation/index.js
+++ b/app/javascript/widget/store/modules/conversation/index.js
@@ -15,6 +15,7 @@ const state = {
},
lastMessageId: null,
totalCsat: 0,
+ totalSubmittedCsat: 0,
totalAnsweredCsat: 0,
csatTemplateEnabled: false,
};
diff --git a/app/javascript/widget/store/modules/conversation/mutations.js b/app/javascript/widget/store/modules/conversation/mutations.js
index 067a70ed36e62..fac4a3958400f 100644
--- a/app/javascript/widget/store/modules/conversation/mutations.js
+++ b/app/javascript/widget/store/modules/conversation/mutations.js
@@ -115,6 +115,10 @@ export const mutations = {
$state.totalAnsweredCsat += 1;
},
+ incrementSubmittedCsat($state) {
+ $state.totalSubmittedCsat += 1;
+ },
+
setCsatTemplateEnabled($state, status) {
$state.csatTemplateEnabled = status;
},
diff --git a/app/models/account.rb b/app/models/account.rb
index d8ffabfaedfda..1b8a19d987c34 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -4,7 +4,8 @@
#
# id :integer not null, primary key
# auto_resolve_duration :integer
-# csat_template_enabled :boolean default(FALSE)
+# csat_template_enabled :boolean default(FALSE), not null
+# csat_trigger :string
# custom_attributes :jsonb
# domain :string(100)
# feature_flags :integer default(0), not null
diff --git a/app/models/csat_survey_response.rb b/app/models/csat_survey_response.rb
index d399850b2dc76..e29feac07f849 100644
--- a/app/models/csat_survey_response.rb
+++ b/app/models/csat_survey_response.rb
@@ -31,7 +31,7 @@ class CsatSurveyResponse < ApplicationRecord
belongs_to :contact
belongs_to :message
belongs_to :assigned_agent, class_name: 'User', optional: true
- belongs_to :csat_template_question
+ belongs_to :csat_template_question, optional: true
validates :rating, presence: true, inclusion: { in: [1, 2, 3, 4, 5] }
validates :account_id, presence: true
diff --git a/app/models/csat_template_question.rb b/app/models/csat_template_question.rb
index 8ef7aa1b4cbec..e30cdd71d8151 100644
--- a/app/models/csat_template_question.rb
+++ b/app/models/csat_template_question.rb
@@ -13,6 +13,17 @@
# index_csat_template_questions_on_csat_template_id (csat_template_id)
#
class CsatTemplateQuestion < ApplicationRecord
- belongs_to :csat_template
+ belongs_to :csat_template, optional: true
has_many :csat_survey_responses, dependent: :nullify
+
+ def self.load_by_content(content)
+ question = find_or_initialize_by(content: content)
+
+ if question.new_record?
+ question.csat_template_id = 0
+ question.save
+ end
+
+ question.reload
+ end
end
diff --git a/app/models/custom_attribute_definition.rb b/app/models/custom_attribute_definition.rb
index a3dfe68e23ece..71aab06f8069f 100644
--- a/app/models/custom_attribute_definition.rb
+++ b/app/models/custom_attribute_definition.rb
@@ -16,7 +16,7 @@
#
# Indexes
#
-# attribute_key_model_index (attribute_key,attribute_model) UNIQUE
+# attribute_key_model_index (attribute_key,attribute_model,account_id) UNIQUE
# index_custom_attribute_definitions_on_account_id (account_id)
#
class CustomAttributeDefinition < ApplicationRecord
diff --git a/app/models/inbox.rb b/app/models/inbox.rb
index 40e2aeb7ae244..c3ba89b2873a1 100644
--- a/app/models/inbox.rb
+++ b/app/models/inbox.rb
@@ -10,6 +10,7 @@
# business_name :string
# channel_type :string
# csat_survey_enabled :boolean default(FALSE)
+# default_reply_action :string
# email_address :string
# enable_auto_assignment :boolean default(TRUE)
# enable_email_collect :boolean default(TRUE)
diff --git a/app/models/message_csat_template_question.rb b/app/models/message_csat_template_question.rb
index e5e8fcb13eae6..8d7e788da927e 100644
--- a/app/models/message_csat_template_question.rb
+++ b/app/models/message_csat_template_question.rb
@@ -4,6 +4,8 @@
#
# id :bigint not null, primary key
# question_number :integer
+# created_at :datetime not null
+# updated_at :datetime not null
# csat_template_question_id :bigint
# message_id :bigint
#
diff --git a/app/views/api/v1/models/_csat_survey_response.json.jbuilder b/app/views/api/v1/models/_csat_survey_response.json.jbuilder
index 98ac6bf443949..185b666e562df 100644
--- a/app/views/api/v1/models/_csat_survey_response.json.jbuilder
+++ b/app/views/api/v1/models/_csat_survey_response.json.jbuilder
@@ -3,10 +3,12 @@ json.rating resource.rating
json.feedback_message resource.feedback_message
json.account_id resource.account_id
json.message_id resource.message_id
+json.csat_question_id resource.message.csat_template_question&.id
if resource.message.csat_template_question
json.csat_question resource.message.csat_template_question.content
- json.csat_question_id resource.message.csat_template_question.id
+else
+ json.csat_question resource.message.content
end
if resource.contact
diff --git a/app/views/api/v1/models/_inbox.json.jbuilder b/app/views/api/v1/models/_inbox.json.jbuilder
index 09db8fdc433bb..9b93961d7bc05 100644
--- a/app/views/api/v1/models/_inbox.json.jbuilder
+++ b/app/views/api/v1/models/_inbox.json.jbuilder
@@ -8,6 +8,7 @@ json.greeting_message resource.greeting_message
json.working_hours_enabled resource.working_hours_enabled
json.enable_email_collect resource.enable_email_collect
json.csat_survey_enabled resource.csat_survey_enabled
+json.default_reply_action resource.default_reply_action
json.enable_auto_assignment resource.enable_auto_assignment
json.auto_assignment_config resource.auto_assignment_config
json.out_of_office_message resource.out_of_office_message
diff --git a/app/views/public/api/v1/models/_inbox.json.jbuilder b/app/views/public/api/v1/models/_inbox.json.jbuilder
index 4865976a8e8bb..62e6b30bb78af 100644
--- a/app/views/public/api/v1/models/_inbox.json.jbuilder
+++ b/app/views/public/api/v1/models/_inbox.json.jbuilder
@@ -3,4 +3,5 @@ json.timezone resource.timezone
json.working_hours resource.weekly_schedule
json.working_hours_enabled resource.working_hours_enabled
json.csat_survey_enabled resource.csat_survey_enabled
+json.default_reply_action resource.default_reply_action
json.greeting_enabled resource.greeting_enabled
diff --git a/config/routes.rb b/config/routes.rb
index 440c927a4732b..47fdb5044d49d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -91,6 +91,7 @@
resource :participants, only: [:show, :create, :update, :destroy]
resource :direct_uploads, only: [:create]
resource :draft_messages, only: [:show, :update, :destroy]
+ resource :reply_action, only: [:show, :update]
end
member do
post :mute
@@ -145,7 +146,9 @@
collection do
get :setting_status
get :inboxes
+ get :csat_trigger
patch :toggle_setting
+ patch :update_csat_trigger
end
end
resources :custom_attribute_definitions, only: [:index, :show, :create, :update, :destroy]
diff --git a/db/migrate/20231209085733_add_inbox_default_reply_action.rb b/db/migrate/20231209085733_add_inbox_default_reply_action.rb
new file mode 100644
index 0000000000000..22e98f8a1bda0
--- /dev/null
+++ b/db/migrate/20231209085733_add_inbox_default_reply_action.rb
@@ -0,0 +1,6 @@
+class AddInboxDefaultReplyAction < ActiveRecord::Migration[7.0]
+ def change
+ add_column :inboxes, :default_reply_action, :string
+ add_column :accounts, :csat_trigger, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 87ce10432a0d5..dcd1198bb9b43 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_11_12_075224) do
+ActiveRecord::Schema[7.0].define(version: 2023_12_09_085733) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements"
enable_extension "pg_trgm"
@@ -54,7 +54,8 @@
t.jsonb "limits", default: {}
t.jsonb "custom_attributes", default: {}
t.integer "status", default: 0
- t.boolean "csat_template_enabled", default: false
+ t.boolean "csat_template_enabled", default: false, null: false
+ t.string "csat_trigger"
t.index ["status"], name: "index_accounts_on_status"
end
@@ -521,7 +522,7 @@
t.text "attribute_description"
t.jsonb "attribute_values", default: []
t.index ["account_id"], name: "index_custom_attribute_definitions_on_account_id"
- t.index ["attribute_key", "attribute_model"], name: "attribute_key_model_index", unique: true
+ t.index ["attribute_key", "attribute_model", "account_id"], name: "attribute_key_model_index", unique: true
end
create_table "custom_filters", force: :cascade do |t|
@@ -610,6 +611,7 @@
t.integer "sender_name_type", default: 0, null: false
t.string "business_name"
t.integer "csat_template_id"
+ t.string "default_reply_action"
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"
@@ -679,6 +681,8 @@
t.bigint "message_id"
t.bigint "csat_template_question_id"
t.integer "question_number"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
t.index ["csat_template_question_id"], name: "index_messages_csat_question_id"
t.index ["message_id"], name: "uniq_csat_question_messages_id"
end
@@ -870,11 +874,9 @@
t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context"
t.index ["taggable_id", "taggable_type", "tagger_id", "context"], name: "taggings_idy"
t.index ["taggable_id"], name: "index_taggings_on_taggable_id"
- t.index ["taggable_type", "taggable_id"], name: "index_taggings_on_taggable_type_and_taggable_id"
t.index ["taggable_type"], name: "index_taggings_on_taggable_type"
t.index ["tagger_id", "tagger_type"], name: "index_taggings_on_tagger_id_and_tagger_type"
t.index ["tagger_id"], name: "index_taggings_on_tagger_id"
- t.index ["tagger_type", "tagger_id"], name: "index_taggings_on_tagger_type_and_tagger_id"
end
create_table "tags", id: :serial, force: :cascade do |t|