feat: Add CSAT response APIs (#2503)

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Sojan Jose
2021-06-29 20:59:41 +05:30
committed by GitHub
parent 2e71006f9d
commit dd9d5e410c
28 changed files with 358 additions and 23 deletions

View File

@@ -0,0 +1,28 @@
class CsatSurveys::ResponseBuilder
pattr_initialize [:message]
def perform
raise 'Invalid Message' unless message.input_csat?
conversation = message.conversation
rating = message.content_attributes.dig('submitted_values', 'csat_survey_response', 'rating')
feedback_message = message.content_attributes.dig('submitted_values', 'csat_survey_response', 'feedback_message')
return if rating.blank?
process_csat_response(conversation, rating, feedback_message)
end
private
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_survey_response.rating = rating
csat_survey_response.feedback_message = feedback_message
csat_survey_response.save!
csat_survey_response
end
end

View File

@@ -1,4 +1,5 @@
class V2::ReportBuilder
include DateRangeHelper
attr_reader :account, :params
def initialize(account, params)
@@ -83,10 +84,6 @@ class V2::ReportBuilder
.average(:value)
end
def range
parse_date_time(params[:since])..parse_date_time(params[:until])
end
# Taking average of average is not too accurate
# https://en.wikipedia.org/wiki/Simpson's_paradox
# TODO: Will optimize this later
@@ -101,11 +98,4 @@ class V2::ReportBuilder
(avg_first_response_time.values.sum / avg_first_response_time.values.length)
end
def parse_date_time(datetime)
return datetime if datetime.is_a?(DateTime)
return datetime.to_datetime if datetime.is_a?(Time) || datetime.is_a?(Date)
DateTime.strptime(datetime, '%s')
end
end

View File

@@ -0,0 +1,22 @@
class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::BaseController
include DateRangeHelper
RESULTS_PER_PAGE = 25
before_action :check_authorization
before_action :csat_survey_responses, only: [:index]
def index; end
private
def csat_survey_responses
@csat_survey_responses = Current.account.csat_survey_responses
@csat_survey_responses = @csat_survey_responses.where(created_at: range) if range.present?
@csat_survey_responses.page(@current_page).per(RESULTS_PER_PAGE)
end
def set_current_page
@current_page = params[:page] || 1
end
end

View File

@@ -54,7 +54,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
end
def message_update_params
params.permit(message: [{ submitted_values: [:name, :title, :value, { csat_survey_response: [:feedback, :rating] }] }])
params.permit(message: [{ submitted_values: [:name, :title, :value, { csat_survey_response: [:feedback_message, :rating] }] }])
end
def permitted_params

View File

@@ -10,10 +10,12 @@ class AsyncDispatcher < BaseDispatcher
def listeners
[
CampaignListener.instance,
CsatSurveyListener.instance,
EventListener.instance,
WebhookListener.instance,
InstallationWebhookListener.instance, HookListener.instance,
CampaignListener.instance
HookListener.instance,
InstallationWebhookListener.instance,
WebhookListener.instance
]
end
end

View File

@@ -0,0 +1,19 @@
##############################################
# Helpers to implement date range filtering to APIs
# Include in your controller or service class where params is available
##############################################
module DateRangeHelper
def range
return if params[:since].blank? || params[:until].blank?
parse_date_time(params[:since])..parse_date_time(params[:until])
end
def parse_date_time(datetime)
return datetime if datetime.is_a?(DateTime)
return datetime.to_datetime if datetime.is_a?(Time) || datetime.is_a?(Date)
DateTime.strptime(datetime, '%s')
end
end

View File

@@ -43,7 +43,7 @@ const generateCSATContent = (
const {
submitted_values: { csat_survey_response: surveyResponse = {} } = {},
} = contentAttributes;
const { rating, feedback } = surveyResponse || {};
const { rating, feedback_message } = surveyResponse || {};
let messageContent = '';
if (rating) {
@@ -53,9 +53,9 @@ const generateCSATContent = (
messageContent += `<div><strong>${ratingTitle}</strong></div>`;
messageContent += `<p>${ratingObject.emoji}</p>`;
}
if (feedback) {
if (feedback_message) {
messageContent += `<div><strong>${feedbackTitle}</strong></div>`;
messageContent += `<p>${feedback}</p>`;
messageContent += `<p>${feedback_message}</p>`;
}
return messageContent;
};

View File

@@ -21,7 +21,10 @@ describe('#generateBotMessageContent', () => {
expect(
generateBotMessageContent('input_csat', {
submitted_values: {
csat_survey_response: { rating: 5, feedback: 'Great Service' },
csat_survey_response: {
rating: 5,
feedback_message: 'Great Service',
},
},
})
).toEqual(
@@ -33,7 +36,7 @@ describe('#generateBotMessageContent', () => {
'input_csat',
{
submitted_values: {
csat_survey_response: { rating: 1, feedback: '' },
csat_survey_response: { rating: 1, feedback_message: '' },
},
},
{ csat: { ratingTitle: 'റേറ്റിംഗ്', feedbackTitle: 'പ്രതികരണം' } }

View File

@@ -130,7 +130,7 @@ export default {
submittedValues: {
csat_survey_response: {
rating,
feedback,
feedback_message: feedback,
},
},
messageId: this.messageId,

View File

@@ -0,0 +1,8 @@
class CsatSurveyListener < BaseListener
def message_updated(event)
message = extract_message_and_account(event)[0]
return unless message.input_csat?
CsatSurveys::ResponseBuilder.new(message: message).perform
end
end

View File

@@ -34,6 +34,7 @@ class Account < ApplicationRecord
has_many :account_users, dependent: :destroy
has_many :agent_bot_inboxes, dependent: :destroy
has_many :agent_bots, dependent: :destroy
has_many :csat_survey_responses, dependent: :destroy
has_many :data_imports, dependent: :destroy
has_many :users, through: :account_users
has_many :inboxes, dependent: :destroy

View File

@@ -39,6 +39,7 @@ class Contact < ApplicationRecord
belongs_to :account
has_many :conversations, dependent: :destroy
has_many :contact_inboxes, dependent: :destroy
has_many :csat_survey_responses, dependent: :destroy
has_many :inboxes, through: :contact_inboxes
has_many :messages, as: :sender, dependent: :destroy
has_many :notes, dependent: :destroy

View File

@@ -60,6 +60,7 @@ class Conversation < ApplicationRecord
belongs_to :campaign, optional: true
has_many :messages, dependent: :destroy, autosave: true
has_one :csat_survey_response, dependent: :destroy
before_create :set_bot_conversation

View File

@@ -0,0 +1,43 @@
# == Schema Information
#
# Table name: csat_survey_responses
#
# id :bigint not null, primary key
# feedback_message :text
# rating :integer not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# assigned_agent_id :bigint
# contact_id :bigint not null
# conversation_id :bigint not null
# message_id :bigint not null
#
# Indexes
#
# index_csat_survey_responses_on_account_id (account_id)
# index_csat_survey_responses_on_assigned_agent_id (assigned_agent_id)
# index_csat_survey_responses_on_contact_id (contact_id)
# index_csat_survey_responses_on_conversation_id (conversation_id)
# index_csat_survey_responses_on_message_id (message_id) UNIQUE
#
# Foreign Keys
#
# fk_rails_... (account_id => accounts.id)
# fk_rails_... (assigned_agent_id => users.id)
# fk_rails_... (contact_id => contacts.id)
# fk_rails_... (conversation_id => conversations.id)
# fk_rails_... (message_id => messages.id)
#
class CsatSurveyResponse < ApplicationRecord
belongs_to :account
belongs_to :conversation
belongs_to :contact
belongs_to :message
belongs_to :assigned_agent, class_name: 'User', optional: true
validates :rating, presence: true, inclusion: { in: [1, 2, 3, 4, 5] }
validates :account_id, presence: true
validates :contact_id, presence: true
validates :conversation_id, presence: true
end

View File

@@ -79,6 +79,7 @@ class Message < ApplicationRecord
belongs_to :sender, polymorphic: true, required: false
has_many :attachments, dependent: :destroy, autosave: true, before_add: :validate_attachments_limit
has_one :csat_survey_response, dependent: :destroy
after_create :reopen_conversation,
:notify_via_mail

View File

@@ -71,6 +71,7 @@ class User < ApplicationRecord
has_many :assigned_conversations, foreign_key: 'assignee_id', class_name: 'Conversation', dependent: :nullify
alias_attribute :conversations, :assigned_conversations
has_many :csat_survey_responses, foreign_key: 'assigned_agent_id', dependent: :nullify
has_many :inbox_members, dependent: :destroy
has_many :inboxes, through: :inbox_members, source: :inbox

View File

@@ -0,0 +1,5 @@
class CsatSurveyResponsePolicy < ApplicationPolicy
def index?
@account_user.administrator?
end
end

View File

@@ -0,0 +1,3 @@
json.array! @csat_survey_responses do |csat_survey_response|
json.partial! 'api/v1/models/csat_survey_response.json.jbuilder', resource: csat_survey_response
end

View File

@@ -0,0 +1,9 @@
json.id resource.id
json.rating resource.rating
json.feedback_message resource.feedback_message
json.account_id resource.account_id
json.message_id resource.message_id
json.contact resource.contact
json.conversation_id resource.conversation.display_id
json.assigned_agent resource.assigned_agent
json.created_at resource.created_at