Compare commits

...
Author SHA1 Message Date
Muhsin KelothandGitHub 652fee75d1 Merge branch 'develop' into feat/seeder-csat 2025-06-26 11:07:55 +05:30
Shivam Mishra 5d2ab0e017 feat: separate csat seeder 2025-06-02 12:49:03 +05:30
Shivam Mishra 6a07efb461 feat: add csat to account seeder 2025-06-02 12:43:07 +05:30
5 changed files with 271 additions and 36 deletions
+7 -32
View File
@@ -25,6 +25,7 @@ class Seeders::AccountSeeder
seed_canned_responses
seed_inboxes
seed_contacts
seed_csat_responses
end
def set_up_account
@@ -116,43 +117,17 @@ class Seeders::AccountSeeder
contact.update!(contact_data.slice('name', 'email'))
Avatar::AvatarFromUrlJob.perform_later(contact, "https://xsgames.co/randomusers/avatar.php?g=#{contact_data['gender']}")
end
contact_data['conversations'].each do |conversation_data|
inbox = @account.inboxes.find_by(channel_type: conversation_data['channel'])
contact_inbox = inbox.contact_inboxes.create_or_find_by!(contact: contact, source_id: (conversation_data['source_id'] || SecureRandom.hex))
create_conversation(contact_inbox: contact_inbox, conversation_data: conversation_data)
end
end
end
def create_conversation(contact_inbox:, conversation_data:)
assignee = User.from_email(conversation_data['assignee']) if conversation_data['assignee'].present?
conversation = contact_inbox.conversations.create!(account: contact_inbox.inbox.account, contact: contact_inbox.contact,
inbox: contact_inbox.inbox, assignee: assignee)
create_messages(conversation: conversation, messages: conversation_data['messages'])
conversation.update_labels(conversation_data[:labels]) if conversation_data[:labels].present?
conversation.update!(priority: conversation_data[:priority]) if conversation_data[:priority].present?
end
def create_messages(conversation:, messages:)
messages.each do |message_data|
sender = find_message_sender(conversation, message_data)
conversation.messages.create!(
message_data.slice('content', 'message_type').merge(
account: conversation.inbox.account, sender: sender, inbox: conversation.inbox
)
)
end
end
def find_message_sender(conversation, message_data)
if message_data['message_type'] == 'incoming'
conversation.contact
elsif message_data['sender'].present?
User.from_email(message_data['sender'])
end
# Create conversations after all contacts are created
Seeders::ConversationSeeder.new(account: @account, contact_data: @account_data['contacts']).perform!
end
def seed_inboxes
Seeders::InboxSeeder.new(account: @account, company_data: @account_data[:company]).perform!
end
def seed_csat_responses
Seeders::CsatSeeder.new(account: @account).perform!
end
end
+94
View File
@@ -0,0 +1,94 @@
## Class to generate sample conversations for a chatwoot test @Account.
############################################################
### Usage #####
#
# # Seed an account with conversations
# Seeders::ConversationSeeder.new(account: Account.find(1), contact_data: []).perform!
#
#
############################################################
class Seeders::ConversationSeeder
def initialize(account:, contact_data:)
raise 'Conversation Seeding is not allowed in production.' unless ENV.fetch('ENABLE_ACCOUNT_SEEDING', !Rails.env.production?)
@account = account
@contact_data = contact_data
end
def perform!
seed_conversations
end
private
def seed_conversations
@contact_data.each do |contact_data|
contact = @account.contacts.find_by(email: contact_data['email'])
next unless contact
contact_data['conversations'].each do |conversation_data|
inbox = @account.inboxes.find_by(channel_type: conversation_data['channel'])
contact_inbox = inbox.contact_inboxes.create_or_find_by!(
contact: contact,
source_id: (conversation_data['source_id'] || SecureRandom.hex)
)
create_conversation(contact_inbox: contact_inbox, conversation_data: conversation_data)
end
end
end
def create_conversation(contact_inbox:, conversation_data:)
assignee = determine_assignee(conversation_data, contact_inbox.inbox)
conversation = build_conversation(contact_inbox, assignee)
create_messages(conversation: conversation, messages: conversation_data['messages'])
apply_conversation_attributes(conversation, conversation_data)
resolve_conversation_if_needed(conversation, assignee)
end
def determine_assignee(conversation_data, inbox)
assignee = User.from_email(conversation_data['assignee']) if conversation_data['assignee'].present?
assignee ||= @account.agents.sample if inbox.csat_survey_enabled
assignee
end
def build_conversation(contact_inbox, assignee)
contact_inbox.conversations.create!(
account: contact_inbox.inbox.account,
contact: contact_inbox.contact,
inbox: contact_inbox.inbox,
assignee: assignee
)
end
def apply_conversation_attributes(conversation, conversation_data)
conversation.update_labels(conversation_data[:labels]) if conversation_data[:labels].present?
conversation.update!(priority: conversation_data[:priority]) if conversation_data[:priority].present?
end
def resolve_conversation_if_needed(conversation, assignee)
return unless assignee.present? && rand > 0.4
conversation.update!(status: :resolved)
end
def create_messages(conversation:, messages:)
messages.each do |message_data|
sender = find_message_sender(conversation, message_data)
conversation.messages.create!(
message_data.slice('content', 'message_type').merge(
account: conversation.inbox.account, sender: sender, inbox: conversation.inbox
)
)
end
end
def find_message_sender(conversation, message_data)
if message_data['message_type'] == 'incoming'
conversation.contact
elsif message_data['sender'].present?
User.from_email(message_data['sender'])
end
end
end
+88
View File
@@ -0,0 +1,88 @@
## Class to generate sample CSAT survey responses for a chatwoot test @Account.
############################################################
### Usage #####
#
# # Seed an account with CSAT responses
# Seeders::CsatSeeder.new(account: Account.find(1)).perform!
#
#
############################################################
class Seeders::CsatSeeder
def initialize(account:)
raise 'CSAT Seeding is not allowed in production.' unless ENV.fetch('ENABLE_ACCOUNT_SEEDING', !Rails.env.production?)
@account = account
end
def perform!
seed_csat_responses
end
private
def seed_csat_responses
resolved_conversations = @account.conversations.where(status: :resolved)
csat_enabled_inboxes = @account.inboxes.where(csat_survey_enabled: true)
return if resolved_conversations.empty? || csat_enabled_inboxes.empty?
# Create CSAT responses for resolved conversations in CSAT-enabled inboxes
resolved_conversations.joins(:inbox).where(inbox: csat_enabled_inboxes).find_each do |conversation|
# Only create CSAT for about 70% of resolved conversations to simulate real-world scenarios
next if rand > 0.7
# Get the last outgoing message to attach CSAT to
last_outgoing_message = conversation.messages.where(message_type: :outgoing).last
next unless last_outgoing_message
# Create CSAT survey response with varied ratings and feedback
create_csat_response(conversation, last_outgoing_message)
end
end
def create_csat_response(conversation, message)
# Generate realistic rating distribution (more positive ratings)
rating = case rand
when 0.0..0.05 then 1 # 5%
when 0.05..0.15 then 2 # 10%
when 0.15..0.35 then 3 # 20%
when 0.35..0.65 then 4 # 30%
else 5 # 35%
end
feedback_messages = [
'Great service, very helpful!',
'Quick response and resolved my issue',
'Professional and courteous support',
'Could be faster but overall good',
'Satisfied with the resolution',
'Excellent customer service',
'Very knowledgeable agent',
'Resolved quickly, thank you!',
'Good experience overall',
'Agent was very patient and helpful',
'Really appreciate the help',
'Fast and efficient service',
'Agent went above and beyond',
'Clear explanations provided',
'Issue resolved on first contact',
'', # Some responses have no feedback
'',
''
]
CsatSurveyResponse.create!(
account: @account,
conversation: conversation,
contact: conversation.contact,
message: message,
assigned_agent: conversation.assignee,
rating: rating,
feedback_message: feedback_messages.sample,
created_at: message.created_at + rand(5..30).minutes
)
rescue StandardError => e
Rails.logger.debug { "Failed to create CSAT response: #{e.message}" }
end
end
+30 -3
View File
@@ -30,13 +30,31 @@ class Seeders::InboxSeeder
def seed_website_inbox
channel = Channel::WebWidget.create!(account: @account, website_url: "https://#{@company_data['domain']}")
Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Website")
Inbox.create!(
channel: channel,
account: @account,
name: "#{@company_data['name']} Website",
csat_survey_enabled: true,
csat_config: {
'display_type' => 'emoji',
'message' => 'How would you rate your experience?'
}
)
end
def seed_facebook_inbox
channel = Channel::FacebookPage.create!(account: @account, user_access_token: SecureRandom.hex, page_access_token: SecureRandom.hex,
page_id: SecureRandom.hex)
Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Facebook")
Inbox.create!(
channel: channel,
account: @account,
name: "#{@company_data['name']} Facebook",
csat_survey_enabled: true,
csat_config: {
'display_type' => 'star',
'message' => 'Please rate your support experience'
}
)
end
def seed_twitter_inbox
@@ -71,7 +89,16 @@ class Seeders::InboxSeeder
def seed_email_inbox
channel = Channel::Email.create!(account: @account, email: "test#{SecureRandom.hex}@#{@company_data['domain']}",
forward_to_email: "test_fwd#{SecureRandom.hex}@#{@company_data['domain']}")
Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Email")
Inbox.create!(
channel: channel,
account: @account,
name: "#{@company_data['name']} Email",
csat_survey_enabled: true,
csat_config: {
'display_type' => 'emoji',
'message' => 'How was your support experience today?'
}
)
end
def seed_api_inbox
+52 -1
View File
@@ -477,4 +477,55 @@ contacts:
- message_type: outgoing
sender: michael_scott@paperlayer.test
content: How may i help you ?
sender: michael_scott@paperlayer.test
- name: "Robert Maxwell"
email: "rmaxwell@example.test"
gender: 'male'
conversations:
- channel: Channel::WebWidget
assignee: karn@paperlayer.test
messages:
- message_type: incoming
content: "Hi, I need help with my account settings."
- message_type: outgoing
sender: karn@paperlayer.test
content: "I'd be happy to help you with your account settings. What specifically would you like to change?"
- message_type: incoming
content: "I want to update my billing information."
- message_type: outgoing
sender: karn@paperlayer.test
content: "Perfect! I've sent you a secure link to update your billing information. Please check your email."
- name: "Sarah Johnson"
email: "sjohnson@demo.test"
gender: 'female'
conversations:
- channel: Channel::FacebookPage
assignee: danny@paperlayer.test
messages:
- message_type: incoming
content: "Hello, I'm having trouble with the mobile app."
- message_type: outgoing
sender: danny@paperlayer.test
content: "Sorry to hear that! Can you tell me what specific issue you're experiencing?"
- message_type: incoming
content: "The app keeps crashing when I try to upload a document."
- message_type: outgoing
sender: danny@paperlayer.test
content: "I understand how frustrating that must be. Let me help you troubleshoot this issue right away."
- name: "David Chen"
email: "dchen@sample.test"
gender: 'male'
conversations:
- channel: Channel::Email
source_id: "dchen@sample.test"
assignee: ben@paperlayer.test
messages:
- message_type: incoming
content: "I need assistance with integrating your API."
- message_type: outgoing
sender: ben@paperlayer.test
content: "I'll be happy to help with the API integration. What programming language are you using?"
- message_type: incoming
content: "We're using Python for our backend."
- message_type: outgoing
sender: ben@paperlayer.test
content: "Great! I'll send you our Python SDK documentation and some sample code to get you started."