Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
158
research/chatwoot/lib/seeders/account_seeder.rb
Normal file
158
research/chatwoot/lib/seeders/account_seeder.rb
Normal file
@@ -0,0 +1,158 @@
|
||||
## Class to generate sample data for a chatwoot test @Account.
|
||||
############################################################
|
||||
### Usage #####
|
||||
#
|
||||
# # Seed an account with all data types in this class
|
||||
# Seeders::AccountSeeder.new(account: Account.find(1)).perform!
|
||||
#
|
||||
#
|
||||
############################################################
|
||||
|
||||
class Seeders::AccountSeeder
|
||||
def initialize(account:)
|
||||
raise 'Account Seeding is not allowed.' unless ENV.fetch('ENABLE_ACCOUNT_SEEDING', !Rails.env.production?)
|
||||
|
||||
@account_data = ActiveSupport::HashWithIndifferentAccess.new(YAML.safe_load(Rails.root.join('lib/seeders/seed_data.yml').read))
|
||||
@account = account
|
||||
end
|
||||
|
||||
def perform!
|
||||
set_up_account
|
||||
seed_teams
|
||||
seed_custom_roles
|
||||
set_up_users
|
||||
seed_labels
|
||||
seed_canned_responses
|
||||
seed_inboxes
|
||||
seed_contacts
|
||||
end
|
||||
|
||||
def set_up_account
|
||||
@account.teams.destroy_all
|
||||
@account.conversations.destroy_all
|
||||
@account.labels.destroy_all
|
||||
@account.inboxes.destroy_all
|
||||
@account.contacts.destroy_all
|
||||
@account.custom_roles.destroy_all if @account.respond_to?(:custom_roles)
|
||||
end
|
||||
|
||||
def seed_teams
|
||||
@account_data['teams'].each do |team_name|
|
||||
@account.teams.create!(name: team_name)
|
||||
end
|
||||
end
|
||||
|
||||
def seed_custom_roles
|
||||
return unless @account_data['custom_roles'].present? && @account.respond_to?(:custom_roles)
|
||||
|
||||
@account_data['custom_roles'].each do |role_data|
|
||||
@account.custom_roles.create!(
|
||||
name: role_data['name'],
|
||||
description: role_data['description'],
|
||||
permissions: role_data['permissions']
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def seed_labels
|
||||
@account_data['labels'].each do |label|
|
||||
@account.labels.create!(label)
|
||||
end
|
||||
end
|
||||
|
||||
def set_up_users
|
||||
@account_data['users'].each do |user|
|
||||
user_record = create_user_record(user)
|
||||
create_account_user(user_record, user)
|
||||
add_user_to_teams(user: user_record, teams: user['team']) if user['team'].present?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_user_record(user)
|
||||
user_record = User.create_with(name: user['name'], password: 'Password1!.').find_or_create_by!(email: user['email'].to_s)
|
||||
user_record.skip_confirmation!
|
||||
user_record.save!
|
||||
Avatar::AvatarFromUrlJob.perform_later(user_record, "https://xsgames.co/randomusers/avatar.php?g=#{user['gender']}")
|
||||
user_record
|
||||
end
|
||||
|
||||
def create_account_user(user_record, user)
|
||||
account_user_attrs = build_account_user_attrs(user)
|
||||
AccountUser.create_with(account_user_attrs).find_or_create_by!(account_id: @account.id, user_id: user_record.id)
|
||||
end
|
||||
|
||||
def build_account_user_attrs(user)
|
||||
attrs = { role: (user['role'] || 'agent') }
|
||||
custom_role = find_custom_role(user['custom_role']) if user['custom_role'].present?
|
||||
attrs[:custom_role] = custom_role if custom_role
|
||||
attrs
|
||||
end
|
||||
|
||||
def add_user_to_teams(user:, teams:)
|
||||
teams.each do |team|
|
||||
team_record = @account.teams.where('name LIKE ?', "%#{team.downcase}%").first if team.present?
|
||||
TeamMember.find_or_create_by!(team_id: team_record.id, user_id: user.id) unless team_record.nil?
|
||||
end
|
||||
end
|
||||
|
||||
def find_custom_role(role_name)
|
||||
return nil unless @account.respond_to?(:custom_roles)
|
||||
|
||||
@account.custom_roles.find_by(name: role_name)
|
||||
end
|
||||
|
||||
def seed_canned_responses(count: 50)
|
||||
count.times do
|
||||
@account.canned_responses.create(content: Faker::Quote.fortune_cookie, short_code: Faker::Alphanumeric.alpha(number: 10))
|
||||
end
|
||||
end
|
||||
|
||||
def seed_contacts
|
||||
@account_data['contacts'].each do |contact_data|
|
||||
contact = @account.contacts.find_or_initialize_by(email: contact_data['email'])
|
||||
if contact.new_record?
|
||||
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
|
||||
end
|
||||
|
||||
def seed_inboxes
|
||||
Seeders::InboxSeeder.new(account: @account, company_data: @account_data[:company]).perform!
|
||||
end
|
||||
end
|
||||
105
research/chatwoot/lib/seeders/inbox_seeder.rb
Normal file
105
research/chatwoot/lib/seeders/inbox_seeder.rb
Normal file
@@ -0,0 +1,105 @@
|
||||
## Class to generate sample inboxes for a chatwoot test @Account.
|
||||
############################################################
|
||||
### Usage #####
|
||||
#
|
||||
# # Seed an account with all data types in this class
|
||||
# Seeders::InboxSeeder.new(account: @Account.find(1), company_data: {name: 'PaperLayer', doamin: 'paperlayer.test'}).perform!
|
||||
#
|
||||
#
|
||||
############################################################
|
||||
|
||||
class Seeders::InboxSeeder
|
||||
def initialize(account:, company_data:)
|
||||
raise 'Inbox Seeding is not allowed in production.' unless ENV.fetch('ENABLE_ACCOUNT_SEEDING', !Rails.env.production?)
|
||||
|
||||
@account = account
|
||||
@company_data = company_data
|
||||
end
|
||||
|
||||
def perform!
|
||||
seed_website_inbox
|
||||
seed_facebook_inbox
|
||||
seed_twitter_inbox
|
||||
seed_whatsapp_inbox
|
||||
seed_sms_inbox
|
||||
seed_email_inbox
|
||||
seed_api_inbox
|
||||
seed_telegram_inbox
|
||||
seed_line_inbox
|
||||
end
|
||||
|
||||
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")
|
||||
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")
|
||||
end
|
||||
|
||||
def seed_twitter_inbox
|
||||
channel = Channel::TwitterProfile.create!(account: @account, twitter_access_token: SecureRandom.hex,
|
||||
twitter_access_token_secret: SecureRandom.hex, profile_id: '123')
|
||||
Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Twitter")
|
||||
end
|
||||
|
||||
def seed_whatsapp_inbox
|
||||
# rubocop:disable Rails/SkipsModelValidations
|
||||
Channel::Whatsapp.insert(
|
||||
{
|
||||
account_id: @account.id,
|
||||
phone_number: Faker::PhoneNumber.cell_phone_in_e164,
|
||||
created_at: Time.now.utc,
|
||||
updated_at: Time.now.utc
|
||||
},
|
||||
returning: %w[id]
|
||||
)
|
||||
# rubocop:enable Rails/SkipsModelValidations
|
||||
|
||||
channel = Channel::Whatsapp.find_by(account_id: @account.id)
|
||||
|
||||
Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Whatsapp")
|
||||
end
|
||||
|
||||
def seed_sms_inbox
|
||||
channel = Channel::Sms.create!(account: @account, phone_number: Faker::PhoneNumber.cell_phone_in_e164)
|
||||
Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Mobile")
|
||||
end
|
||||
|
||||
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")
|
||||
end
|
||||
|
||||
def seed_api_inbox
|
||||
channel = Channel::Api.create!(account: @account)
|
||||
Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} API")
|
||||
end
|
||||
|
||||
def seed_telegram_inbox
|
||||
# rubocop:disable Rails/SkipsModelValidations
|
||||
bot_token = SecureRandom.hex
|
||||
Channel::Telegram.insert(
|
||||
{
|
||||
account_id: @account.id,
|
||||
bot_name: (@company_data['name']).to_s,
|
||||
bot_token: bot_token,
|
||||
created_at: Time.now.utc,
|
||||
updated_at: Time.now.utc
|
||||
},
|
||||
returning: %w[id]
|
||||
)
|
||||
channel = Channel::Telegram.find_by(bot_token: bot_token)
|
||||
Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Telegram")
|
||||
# rubocop:enable Rails/SkipsModelValidations
|
||||
end
|
||||
|
||||
def seed_line_inbox
|
||||
channel = Channel::Line.create!(account: @account, line_channel_id: SecureRandom.hex, line_channel_secret: SecureRandom.hex,
|
||||
line_channel_token: SecureRandom.hex)
|
||||
Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Line")
|
||||
end
|
||||
end
|
||||
123
research/chatwoot/lib/seeders/message_seeder.rb
Normal file
123
research/chatwoot/lib/seeders/message_seeder.rb
Normal file
@@ -0,0 +1,123 @@
|
||||
module Seeders::MessageSeeder
|
||||
def self.create_sample_email_collect_message(conversation)
|
||||
Message.create!(
|
||||
account: conversation.account,
|
||||
inbox: conversation.inbox,
|
||||
conversation: conversation,
|
||||
message_type: :template,
|
||||
content_type: :input_email,
|
||||
content: 'Get notified by email'
|
||||
)
|
||||
end
|
||||
|
||||
def self.create_sample_csat_collect_message(conversation)
|
||||
Message.create!(
|
||||
account: conversation.account,
|
||||
inbox: conversation.inbox,
|
||||
conversation: conversation,
|
||||
message_type: :template,
|
||||
content_type: :input_csat,
|
||||
content: 'Please rate the support'
|
||||
)
|
||||
end
|
||||
|
||||
def self.create_sample_cards_message(conversation)
|
||||
Message.create!(
|
||||
account: conversation.account,
|
||||
inbox: conversation.inbox,
|
||||
conversation: conversation,
|
||||
message_type: :template,
|
||||
content_type: 'cards',
|
||||
content: 'cards',
|
||||
content_attributes: {
|
||||
items: [
|
||||
sample_card_item,
|
||||
sample_card_item
|
||||
]
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def self.sample_card_item
|
||||
{
|
||||
media_url: 'https://i.imgur.com/d8Djr4k.jpg',
|
||||
title: 'Acme Shoes 2.0',
|
||||
description: 'Move with Acme Shoe 2.0',
|
||||
actions: [
|
||||
{
|
||||
type: 'link',
|
||||
text: 'View More',
|
||||
uri: 'http://acme-shoes.inc'
|
||||
},
|
||||
{
|
||||
type: 'postback',
|
||||
text: 'Add to cart',
|
||||
payload: 'ITEM_SELECTED'
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def self.create_sample_input_select_message(conversation)
|
||||
Message.create!(
|
||||
account: conversation.account,
|
||||
inbox: conversation.inbox,
|
||||
conversation: conversation,
|
||||
message_type: :template,
|
||||
content: 'Your favorite food',
|
||||
content_type: 'input_select',
|
||||
content_attributes: {
|
||||
items: [
|
||||
{ title: '🌯 Burito', value: 'Burito' },
|
||||
{ title: '🍝 Pasta', value: 'Pasta' },
|
||||
{ title: ' 🍱 Sushi', value: 'Sushi' },
|
||||
{ title: ' 🥗 Salad', value: 'Salad' }
|
||||
]
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def self.create_sample_form_message(conversation)
|
||||
Message.create!(
|
||||
account: conversation.account,
|
||||
inbox: conversation.inbox,
|
||||
conversation: conversation,
|
||||
message_type: :template,
|
||||
content_type: 'form',
|
||||
content: 'form',
|
||||
content_attributes: sample_form
|
||||
)
|
||||
end
|
||||
|
||||
def self.sample_form
|
||||
{
|
||||
items: [
|
||||
{ name: 'email', placeholder: 'Please enter your email', type: 'email', label: 'Email', required: 'required',
|
||||
pattern_error: 'Please fill this field', pattern: '^[^\s@]+@[^\s@]+\.[^\s@]+$' },
|
||||
{ name: 'text_area', placeholder: 'Please enter text', type: 'text_area', label: 'Large Text', required: 'required',
|
||||
pattern_error: 'Please fill this field' },
|
||||
{ name: 'text', placeholder: 'Please enter text', type: 'text', label: 'text', default: 'defaut value', required: 'required',
|
||||
pattern: '^[a-zA-Z ]*$', pattern_error: 'Only alphabets are allowed' },
|
||||
{ name: 'select', label: 'Select Option', type: 'select', options: [{ label: '🌯 Burito', value: 'Burito' },
|
||||
{ label: '🍝 Pasta', value: 'Pasta' }] }
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def self.create_sample_articles_message(conversation)
|
||||
Message.create!(
|
||||
account: conversation.account,
|
||||
inbox: conversation.inbox,
|
||||
conversation: conversation,
|
||||
message_type: :template,
|
||||
content: 'Tech Companies',
|
||||
content_type: 'article',
|
||||
content_attributes: {
|
||||
items: [
|
||||
{ title: 'Acme Hardware', description: 'Hardware reimagined', link: 'http://acme-hardware.inc' },
|
||||
{ title: 'Acme Search', description: 'The best Search Engine', link: 'http://acme-search.inc' }
|
||||
]
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
119
research/chatwoot/lib/seeders/reports/conversation_creator.rb
Normal file
119
research/chatwoot/lib/seeders/reports/conversation_creator.rb
Normal file
@@ -0,0 +1,119 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'faker'
|
||||
require 'active_support/testing/time_helpers'
|
||||
|
||||
class Seeders::Reports::ConversationCreator
|
||||
include ActiveSupport::Testing::TimeHelpers
|
||||
|
||||
def initialize(account:, resources:)
|
||||
@account = account
|
||||
@contacts = resources[:contacts]
|
||||
@inboxes = resources[:inboxes]
|
||||
@teams = resources[:teams]
|
||||
@labels = resources[:labels]
|
||||
@agents = resources[:agents]
|
||||
@priorities = [nil, 'urgent', 'high', 'medium', 'low']
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def create_conversation(created_at:)
|
||||
conversation = nil
|
||||
should_resolve = false
|
||||
resolution_time = nil
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
travel_to(created_at) do
|
||||
conversation = build_conversation
|
||||
conversation.save!
|
||||
|
||||
add_labels_to_conversation(conversation)
|
||||
create_messages_for_conversation(conversation)
|
||||
|
||||
# Determine if should resolve but don't update yet
|
||||
should_resolve = rand > 0.3
|
||||
if should_resolve
|
||||
resolution_delay = rand((30.minutes)..(24.hours))
|
||||
resolution_time = created_at + resolution_delay
|
||||
end
|
||||
end
|
||||
|
||||
travel_back
|
||||
end
|
||||
|
||||
# Now resolve outside of time travel if needed
|
||||
if should_resolve && resolution_time
|
||||
# rubocop:disable Rails/SkipsModelValidations
|
||||
conversation.update_column(:status, :resolved)
|
||||
conversation.update_column(:updated_at, resolution_time)
|
||||
# rubocop:enable Rails/SkipsModelValidations
|
||||
|
||||
# Trigger the event with proper timestamp
|
||||
travel_to(resolution_time) do
|
||||
trigger_conversation_resolved_event(conversation)
|
||||
end
|
||||
travel_back
|
||||
end
|
||||
|
||||
conversation
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
private
|
||||
|
||||
def build_conversation
|
||||
contact = @contacts.sample
|
||||
inbox = @inboxes.sample
|
||||
|
||||
contact_inbox = find_or_create_contact_inbox(contact, inbox)
|
||||
assignee = select_assignee(inbox)
|
||||
team = select_team
|
||||
priority = @priorities.sample
|
||||
|
||||
contact_inbox.conversations.new(
|
||||
account: @account,
|
||||
inbox: inbox,
|
||||
contact: contact,
|
||||
assignee: assignee,
|
||||
team: team,
|
||||
priority: priority
|
||||
)
|
||||
end
|
||||
|
||||
def find_or_create_contact_inbox(contact, inbox)
|
||||
inbox.contact_inboxes.find_or_create_by!(
|
||||
contact: contact,
|
||||
source_id: SecureRandom.hex
|
||||
)
|
||||
end
|
||||
|
||||
def select_assignee(inbox)
|
||||
rand(10) < 8 ? inbox.members.sample : nil
|
||||
end
|
||||
|
||||
def select_team
|
||||
rand(10) < 7 ? @teams.sample : nil
|
||||
end
|
||||
|
||||
def add_labels_to_conversation(conversation)
|
||||
labels_to_add = @labels.sample(rand(5..20))
|
||||
conversation.update_labels(labels_to_add.map(&:title))
|
||||
end
|
||||
|
||||
def create_messages_for_conversation(conversation)
|
||||
message_creator = Seeders::Reports::MessageCreator.new(
|
||||
account: @account,
|
||||
agents: @agents,
|
||||
conversation: conversation
|
||||
)
|
||||
message_creator.create_messages
|
||||
end
|
||||
|
||||
def trigger_conversation_resolved_event(conversation)
|
||||
event_data = { conversation: conversation }
|
||||
|
||||
ReportingEventListener.instance.conversation_resolved(
|
||||
Events::Base.new('conversation_resolved', Time.current, event_data)
|
||||
)
|
||||
end
|
||||
end
|
||||
141
research/chatwoot/lib/seeders/reports/message_creator.rb
Normal file
141
research/chatwoot/lib/seeders/reports/message_creator.rb
Normal file
@@ -0,0 +1,141 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'faker'
|
||||
require 'active_support/testing/time_helpers'
|
||||
|
||||
class Seeders::Reports::MessageCreator
|
||||
include ActiveSupport::Testing::TimeHelpers
|
||||
|
||||
MESSAGES_PER_CONVERSATION = 5
|
||||
|
||||
def initialize(account:, agents:, conversation:)
|
||||
@account = account
|
||||
@agents = agents
|
||||
@conversation = conversation
|
||||
end
|
||||
|
||||
def create_messages
|
||||
message_count = rand(MESSAGES_PER_CONVERSATION..MESSAGES_PER_CONVERSATION + 5)
|
||||
first_agent_reply = true
|
||||
|
||||
message_count.times do |i|
|
||||
message = create_single_message(i)
|
||||
first_agent_reply = handle_reply_tracking(message, i, first_agent_reply)
|
||||
end
|
||||
end
|
||||
|
||||
def create_single_message(index)
|
||||
is_incoming = index.even?
|
||||
add_realistic_delay(index, is_incoming) if index.positive?
|
||||
create_message(is_incoming)
|
||||
end
|
||||
|
||||
def handle_reply_tracking(message, index, first_agent_reply)
|
||||
return first_agent_reply if index.even? # Skip incoming messages
|
||||
|
||||
handle_agent_reply_events(message, first_agent_reply)
|
||||
false # No longer first reply after any agent message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_realistic_delay(_message_index, is_incoming)
|
||||
delay = calculate_message_delay(is_incoming)
|
||||
travel(delay)
|
||||
end
|
||||
|
||||
def calculate_message_delay(is_incoming)
|
||||
if is_incoming
|
||||
# Customer response time: 1 minute to 4 hours
|
||||
rand((1.minute)..(4.hours))
|
||||
elsif business_hours_active?(Time.current)
|
||||
# Agent response time varies by business hours
|
||||
rand((30.seconds)..(30.minutes))
|
||||
else
|
||||
rand((1.hour)..(8.hours))
|
||||
end
|
||||
end
|
||||
|
||||
def create_message(is_incoming)
|
||||
if is_incoming
|
||||
create_incoming_message
|
||||
else
|
||||
create_outgoing_message
|
||||
end
|
||||
end
|
||||
|
||||
def create_incoming_message
|
||||
@conversation.messages.create!(
|
||||
account: @account,
|
||||
inbox: @conversation.inbox,
|
||||
message_type: :incoming,
|
||||
content: generate_message_content,
|
||||
sender: @conversation.contact
|
||||
)
|
||||
end
|
||||
|
||||
def create_outgoing_message
|
||||
sender = @conversation.assignee || @agents.sample
|
||||
|
||||
@conversation.messages.create!(
|
||||
account: @account,
|
||||
inbox: @conversation.inbox,
|
||||
message_type: :outgoing,
|
||||
content: generate_message_content,
|
||||
sender: sender
|
||||
)
|
||||
end
|
||||
|
||||
def generate_message_content
|
||||
Faker::Lorem.paragraph(sentence_count: rand(1..5))
|
||||
end
|
||||
|
||||
def handle_agent_reply_events(message, is_first_reply)
|
||||
if is_first_reply
|
||||
trigger_first_reply_event(message)
|
||||
else
|
||||
trigger_reply_event(message)
|
||||
end
|
||||
end
|
||||
|
||||
def business_hours_active?(time)
|
||||
weekday = time.wday
|
||||
hour = time.hour
|
||||
weekday.between?(1, 5) && hour.between?(9, 17)
|
||||
end
|
||||
|
||||
def trigger_first_reply_event(message)
|
||||
event_data = {
|
||||
message: message,
|
||||
conversation: message.conversation
|
||||
}
|
||||
|
||||
ReportingEventListener.instance.first_reply_created(
|
||||
Events::Base.new('first_reply_created', Time.current, event_data)
|
||||
)
|
||||
end
|
||||
|
||||
def trigger_reply_event(message)
|
||||
waiting_since = calculate_waiting_since(message)
|
||||
|
||||
event_data = {
|
||||
message: message,
|
||||
conversation: message.conversation,
|
||||
waiting_since: waiting_since
|
||||
}
|
||||
|
||||
ReportingEventListener.instance.reply_created(
|
||||
Events::Base.new('reply_created', Time.current, event_data)
|
||||
)
|
||||
end
|
||||
|
||||
def calculate_waiting_since(message)
|
||||
last_customer_message = message.conversation.messages
|
||||
.where(message_type: :incoming)
|
||||
.where('created_at < ?', message.created_at)
|
||||
.order(:created_at)
|
||||
.last
|
||||
|
||||
last_customer_message&.created_at || message.conversation.created_at
|
||||
end
|
||||
end
|
||||
234
research/chatwoot/lib/seeders/reports/report_data_seeder.rb
Normal file
234
research/chatwoot/lib/seeders/reports/report_data_seeder.rb
Normal file
@@ -0,0 +1,234 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Reports Data Seeder
|
||||
#
|
||||
# Generates realistic test data for performance testing of reports and analytics.
|
||||
# Creates conversations, messages, contacts, agents, teams, and labels with proper
|
||||
# reporting events (first response times, resolution times, etc.) using time travel
|
||||
# to generate historical data with realistic timestamps.
|
||||
#
|
||||
# Usage:
|
||||
# ACCOUNT_ID=1 ENABLE_ACCOUNT_SEEDING=true bundle exec rake db:seed:reports_data
|
||||
#
|
||||
# This will create:
|
||||
# - 1000 conversations with realistic message exchanges
|
||||
# - 100 contacts with realistic profiles
|
||||
# - 20 agents assigned to teams and inboxes
|
||||
# - 5 teams with realistic distribution
|
||||
# - 30 labels with random assignments
|
||||
# - 3 inboxes with agent assignments
|
||||
# - Realistic reporting events with historical timestamps
|
||||
#
|
||||
# Note: This seeder clears existing data for the account before seeding.
|
||||
|
||||
require 'faker'
|
||||
require_relative 'conversation_creator'
|
||||
require_relative 'message_creator'
|
||||
|
||||
# rubocop:disable Rails/Output
|
||||
class Seeders::Reports::ReportDataSeeder
|
||||
include ActiveSupport::Testing::TimeHelpers
|
||||
|
||||
TOTAL_CONVERSATIONS = 1000
|
||||
TOTAL_CONTACTS = 100
|
||||
TOTAL_AGENTS = 20
|
||||
TOTAL_TEAMS = 5
|
||||
TOTAL_LABELS = 30
|
||||
TOTAL_INBOXES = 3
|
||||
MESSAGES_PER_CONVERSATION = 5
|
||||
START_DATE = 3.months.ago # rubocop:disable Rails/RelativeDateConstant
|
||||
END_DATE = Time.current
|
||||
|
||||
def initialize(account:)
|
||||
raise 'Account Seeding is not allowed.' unless ENV.fetch('ENABLE_ACCOUNT_SEEDING', !Rails.env.production?)
|
||||
|
||||
@account = account
|
||||
@teams = []
|
||||
@agents = []
|
||||
@labels = []
|
||||
@inboxes = []
|
||||
@contacts = []
|
||||
end
|
||||
|
||||
def perform!
|
||||
puts "Starting reports data seeding for account: #{@account.name}"
|
||||
|
||||
# Clear existing data
|
||||
clear_existing_data
|
||||
|
||||
create_teams
|
||||
create_agents
|
||||
create_labels
|
||||
create_inboxes
|
||||
create_contacts
|
||||
|
||||
create_conversations
|
||||
|
||||
puts "Completed reports data seeding for account: #{@account.name}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def clear_existing_data
|
||||
puts "Clearing existing data for account: #{@account.id}"
|
||||
@account.teams.destroy_all
|
||||
@account.conversations.destroy_all
|
||||
@account.labels.destroy_all
|
||||
@account.inboxes.destroy_all
|
||||
@account.contacts.destroy_all
|
||||
@account.agents.destroy_all
|
||||
@account.reporting_events.destroy_all
|
||||
end
|
||||
|
||||
def create_teams
|
||||
TOTAL_TEAMS.times do |i|
|
||||
team = @account.teams.create!(
|
||||
name: "#{Faker::Company.industry} Team #{i + 1}"
|
||||
)
|
||||
@teams << team
|
||||
print "\rCreating teams: #{i + 1}/#{TOTAL_TEAMS}"
|
||||
end
|
||||
|
||||
print "\n"
|
||||
end
|
||||
|
||||
def create_agents
|
||||
TOTAL_AGENTS.times do |i|
|
||||
user = create_single_agent(i)
|
||||
assign_agent_to_teams(user)
|
||||
@agents << user
|
||||
print "\rCreating agents: #{i + 1}/#{TOTAL_AGENTS}"
|
||||
end
|
||||
|
||||
print "\n"
|
||||
end
|
||||
|
||||
def create_single_agent(index)
|
||||
random_suffix = SecureRandom.hex(4)
|
||||
user = User.create!(
|
||||
name: Faker::Name.name,
|
||||
email: "agent_#{index + 1}_#{random_suffix}@#{@account.domain || 'example.com'}",
|
||||
password: 'Password1!.',
|
||||
confirmed_at: Time.current
|
||||
)
|
||||
user.skip_confirmation!
|
||||
user.save!
|
||||
|
||||
AccountUser.create!(
|
||||
account_id: @account.id,
|
||||
user_id: user.id,
|
||||
role: :agent
|
||||
)
|
||||
|
||||
user
|
||||
end
|
||||
|
||||
def assign_agent_to_teams(user)
|
||||
teams_to_assign = @teams.sample(rand(1..3))
|
||||
teams_to_assign.each do |team|
|
||||
TeamMember.create!(
|
||||
team_id: team.id,
|
||||
user_id: user.id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def create_labels
|
||||
TOTAL_LABELS.times do |i|
|
||||
label = @account.labels.create!(
|
||||
title: "Label-#{i + 1}-#{Faker::Lorem.word}",
|
||||
description: Faker::Company.catch_phrase,
|
||||
color: Faker::Color.hex_color
|
||||
)
|
||||
@labels << label
|
||||
print "\rCreating labels: #{i + 1}/#{TOTAL_LABELS}"
|
||||
end
|
||||
|
||||
print "\n"
|
||||
end
|
||||
|
||||
def create_inboxes
|
||||
TOTAL_INBOXES.times do |_i|
|
||||
inbox = create_single_inbox
|
||||
assign_agents_to_inbox(inbox)
|
||||
@inboxes << inbox
|
||||
print "\rCreating inboxes: #{@inboxes.size}/#{TOTAL_INBOXES}"
|
||||
end
|
||||
|
||||
print "\n"
|
||||
end
|
||||
|
||||
def create_single_inbox
|
||||
channel = Channel::WebWidget.create!(
|
||||
website_url: "https://#{Faker::Internet.domain_name}",
|
||||
account_id: @account.id
|
||||
)
|
||||
|
||||
@account.inboxes.create!(
|
||||
name: "#{Faker::Company.name} Website",
|
||||
channel: channel
|
||||
)
|
||||
end
|
||||
|
||||
def assign_agents_to_inbox(inbox)
|
||||
agents_to_assign = if @inboxes.empty?
|
||||
# First inbox gets all agents to ensure coverage
|
||||
@agents
|
||||
else
|
||||
# Subsequent inboxes get random selection with some overlap
|
||||
min_agents = [@agents.size / TOTAL_INBOXES, 10].max
|
||||
max_agents = [(@agents.size * 0.8).to_i, 50].min
|
||||
@agents.sample(rand(min_agents..max_agents))
|
||||
end
|
||||
|
||||
agents_to_assign.each do |agent|
|
||||
InboxMember.create!(inbox: inbox, user: agent)
|
||||
end
|
||||
end
|
||||
|
||||
def create_contacts
|
||||
TOTAL_CONTACTS.times do |i|
|
||||
contact = @account.contacts.create!(
|
||||
name: Faker::Name.name,
|
||||
email: Faker::Internet.email,
|
||||
phone_number: Faker::PhoneNumber.cell_phone_in_e164,
|
||||
identifier: SecureRandom.uuid,
|
||||
additional_attributes: {
|
||||
company: Faker::Company.name,
|
||||
city: Faker::Address.city,
|
||||
country: Faker::Address.country,
|
||||
customer_since: Faker::Date.between(from: 2.years.ago, to: Time.zone.today)
|
||||
}
|
||||
)
|
||||
@contacts << contact
|
||||
|
||||
print "\rCreating contacts: #{i + 1}/#{TOTAL_CONTACTS}"
|
||||
end
|
||||
|
||||
print "\n"
|
||||
end
|
||||
|
||||
def create_conversations
|
||||
conversation_creator = Seeders::Reports::ConversationCreator.new(
|
||||
account: @account,
|
||||
resources: {
|
||||
contacts: @contacts,
|
||||
inboxes: @inboxes,
|
||||
teams: @teams,
|
||||
labels: @labels,
|
||||
agents: @agents
|
||||
}
|
||||
)
|
||||
|
||||
TOTAL_CONVERSATIONS.times do |i|
|
||||
created_at = Faker::Time.between(from: START_DATE, to: END_DATE)
|
||||
conversation_creator.create_conversation(created_at: created_at)
|
||||
|
||||
completion_percentage = ((i + 1).to_f / TOTAL_CONVERSATIONS * 100).round
|
||||
print "\rCreating conversations: #{i + 1}/#{TOTAL_CONVERSATIONS} (#{completion_percentage}%)"
|
||||
end
|
||||
|
||||
print "\n"
|
||||
end
|
||||
end
|
||||
# rubocop:enable Rails/Output
|
||||
480
research/chatwoot/lib/seeders/seed_data.yml
Normal file
480
research/chatwoot/lib/seeders/seed_data.yml
Normal file
@@ -0,0 +1,480 @@
|
||||
company:
|
||||
name: 'PaperLayer'
|
||||
domain: 'paperlayer.test'
|
||||
users:
|
||||
- name: 'Michael Scott'
|
||||
gender: male
|
||||
email: 'michael_scott@paperlayer.test'
|
||||
team:
|
||||
- 'sales'
|
||||
- 'management'
|
||||
- 'administration'
|
||||
- 'warehouse'
|
||||
role: 'administrator'
|
||||
- name: 'David Wallace'
|
||||
gender: male
|
||||
email: 'david@paperlayer.test'
|
||||
team:
|
||||
- 'Management'
|
||||
- name: 'Deangelo Vickers'
|
||||
gender: male
|
||||
email: 'deangelo@paperlayer.test'
|
||||
team:
|
||||
- 'Management'
|
||||
- name: 'Jo Bennett'
|
||||
gender: female
|
||||
email: 'jo@paperlayer.test'
|
||||
team:
|
||||
- 'Management'
|
||||
- name: 'Josh Porter'
|
||||
gender: male
|
||||
email: 'josh@paperlayer.test'
|
||||
team:
|
||||
- 'Management'
|
||||
- name: 'Charles Miner'
|
||||
gender: male
|
||||
email: 'charles@paperlayer.test'
|
||||
team:
|
||||
- 'Management'
|
||||
- name: 'Ed Truck'
|
||||
gender: male
|
||||
email: 'ed@paperlayer.test'
|
||||
team:
|
||||
- 'Management'
|
||||
- name: 'Dan Gore'
|
||||
gender: male
|
||||
email: 'dan@paperlayer.test'
|
||||
team:
|
||||
- 'Management'
|
||||
- name: 'Craig D'
|
||||
gender: male
|
||||
email: 'craig@paperlayer.test'
|
||||
team:
|
||||
- 'Management'
|
||||
- name: 'Troy Underbridge'
|
||||
gender: male
|
||||
email: 'troy@paperlayer.test'
|
||||
team:
|
||||
- 'Management'
|
||||
- name: 'Karen Filippelli'
|
||||
gender: female
|
||||
email: 'karn@paperlayer.test'
|
||||
team:
|
||||
- 'Sales'
|
||||
custom_role: 'Sales Representative'
|
||||
- name: 'Danny Cordray'
|
||||
gender: female
|
||||
email: 'danny@paperlayer.test'
|
||||
team:
|
||||
- 'Sales'
|
||||
custom_role: 'Customer Support Lead'
|
||||
- name: 'Ben Nugent'
|
||||
gender: male
|
||||
email: 'ben@paperlayer.test'
|
||||
team:
|
||||
- 'Sales'
|
||||
custom_role: 'Junior Agent'
|
||||
- name: 'Todd Packer'
|
||||
gender: male
|
||||
email: 'todd@paperlayer.test'
|
||||
team:
|
||||
- 'Sales'
|
||||
custom_role: 'Sales Representative'
|
||||
- name: 'Cathy Simms'
|
||||
gender: female
|
||||
email: 'cathy@paperlayer.test'
|
||||
team:
|
||||
- 'Administration'
|
||||
custom_role: 'Knowledge Manager'
|
||||
- name: 'Hunter Jo'
|
||||
gender: male
|
||||
email: 'hunter@paperlayer.test'
|
||||
team:
|
||||
- 'Administration'
|
||||
custom_role: 'Analytics Specialist'
|
||||
- name: 'Rolando Silva'
|
||||
gender: male
|
||||
email: 'rolando@paperlayer.test'
|
||||
team:
|
||||
- 'Administration'
|
||||
custom_role: 'Junior Agent'
|
||||
- name: 'Stephanie Wilson'
|
||||
gender: female
|
||||
email: 'stephanie@paperlayer.test'
|
||||
team:
|
||||
- 'Administration'
|
||||
custom_role: 'Escalation Handler'
|
||||
- name: 'Jordan Garfield'
|
||||
gender: male
|
||||
email: 'jorodan@paperlayer.test'
|
||||
team:
|
||||
- 'Administration'
|
||||
- name: 'Ronni Carlo'
|
||||
gender: male
|
||||
email: 'ronni@paperlayer.test'
|
||||
team:
|
||||
- 'Administration'
|
||||
- name: 'Lonny Collins'
|
||||
gender: female
|
||||
email: 'lonny@paperlayer.test'
|
||||
team:
|
||||
- 'Warehouse'
|
||||
custom_role: 'Customer Support Lead'
|
||||
- name: 'Madge Madsen'
|
||||
gender: female
|
||||
email: 'madge@paperlayer.test'
|
||||
team:
|
||||
- 'Warehouse'
|
||||
- name: 'Glenn Max'
|
||||
gender: female
|
||||
email: 'glenn@paperlayer.test'
|
||||
team:
|
||||
- 'Warehouse'
|
||||
- name: 'Jerry DiCanio'
|
||||
gender: male
|
||||
email: 'jerry@paperlayer.test'
|
||||
team:
|
||||
- 'Warehouse'
|
||||
- name: 'Phillip Martin'
|
||||
gender: male
|
||||
email: 'phillip@paperlayer.test'
|
||||
team:
|
||||
- 'Warehouse'
|
||||
- name: 'Michael Josh'
|
||||
gender: male
|
||||
email: 'michale_josh@paperlayer.test'
|
||||
team:
|
||||
- 'Warehouse'
|
||||
- name: 'Matt Hudson'
|
||||
gender: male
|
||||
email: 'matt@paperlayer.test'
|
||||
team:
|
||||
- 'Warehouse'
|
||||
- name: 'Gideon'
|
||||
gender: male
|
||||
email: 'gideon@paperlayer.test'
|
||||
team:
|
||||
- 'Warehouse'
|
||||
- name: 'Bruce'
|
||||
gender: male
|
||||
email: 'bruce@paperlayer.test'
|
||||
team:
|
||||
- 'Warehouse'
|
||||
- name: 'Frank'
|
||||
gender: male
|
||||
email: 'frank@paperlayer.test'
|
||||
team:
|
||||
- 'Warehouse'
|
||||
- name: 'Louanne Kelley'
|
||||
gender: female
|
||||
email: 'louanne@paperlayer.test'
|
||||
- name: 'Devon White'
|
||||
gender: male
|
||||
email: 'devon@paperlayer.test'
|
||||
custom_role: 'Escalation Handler'
|
||||
- name: 'Kendall'
|
||||
gender: male
|
||||
email: 'kendall@paperlayer.test'
|
||||
- email: 'sadiq@paperlayer.test'
|
||||
name: 'Sadiq'
|
||||
gender: male
|
||||
teams:
|
||||
- '💰 Sales'
|
||||
- '💼 Management'
|
||||
- '👩💼 Administration'
|
||||
- '🚛 Warehouse'
|
||||
custom_roles:
|
||||
- name: 'Customer Support Lead'
|
||||
description: 'Lead support agent with full conversation and contact management'
|
||||
permissions:
|
||||
- 'conversation_manage'
|
||||
- 'contact_manage'
|
||||
- 'report_manage'
|
||||
- name: 'Sales Representative'
|
||||
description: 'Sales team member with conversation and contact access'
|
||||
permissions:
|
||||
- 'conversation_unassigned_manage'
|
||||
- 'conversation_participating_manage'
|
||||
- 'contact_manage'
|
||||
- name: 'Knowledge Manager'
|
||||
description: 'Manages knowledge base and participates in conversations'
|
||||
permissions:
|
||||
- 'knowledge_base_manage'
|
||||
- 'conversation_participating_manage'
|
||||
- name: 'Junior Agent'
|
||||
description: 'Entry-level agent with basic conversation access'
|
||||
permissions:
|
||||
- 'conversation_participating_manage'
|
||||
- name: 'Analytics Specialist'
|
||||
description: 'Focused on reports and data analysis'
|
||||
permissions:
|
||||
- 'report_manage'
|
||||
- 'conversation_participating_manage'
|
||||
- name: 'Escalation Handler'
|
||||
description: 'Handles unassigned conversations and escalations'
|
||||
permissions:
|
||||
- 'conversation_unassigned_manage'
|
||||
- 'conversation_participating_manage'
|
||||
- 'contact_manage'
|
||||
labels:
|
||||
- title: 'billing'
|
||||
color: '#28AD21'
|
||||
show_on_sidebar: true
|
||||
- title: 'software'
|
||||
color: '#8F6EF2'
|
||||
show_on_sidebar: true
|
||||
- title: 'delivery'
|
||||
color: '#A2FDD5'
|
||||
show_on_sidebar: true
|
||||
- title: 'ops-handover'
|
||||
color: '#A53326'
|
||||
show_on_sidebar: true
|
||||
- title: 'premium-customer'
|
||||
color: '#6FD4EF'
|
||||
show_on_sidebar: true
|
||||
- title: 'lead'
|
||||
color: '#F161C8'
|
||||
show_on_sidebar: true
|
||||
contacts:
|
||||
- name: "Lorrie Trosdall"
|
||||
email: "ltrosdall0@bravesites.test"
|
||||
gender: 'female'
|
||||
conversations:
|
||||
- channel: Channel::WebWidget
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I'm having trouble logging in to my account.
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: Hi! Sorry to hear that. Can you please provide me with your username and email address so I can look into it for you?
|
||||
- name: "Tiffanie Cloughton"
|
||||
email: "tcloughton1@newyorker.test"
|
||||
gender: 'female'
|
||||
conversations:
|
||||
- channel: Channel::FacebookPage
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I need some help with my billing statement.
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: Hello! I'd be happy to assist you with that. Can you please tell me which billing statement you're referring to?
|
||||
- name: "Melonie Keatch"
|
||||
email: "mkeatch2@reuters.test"
|
||||
gender: 'female'
|
||||
conversations:
|
||||
- channel: Channel::TwitterProfile
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I think I accidentally deleted some important files. Can you help me recover them?
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: Of course! Can you please tell me what type of files they were and where they were located on your device?
|
||||
- name: "Olin Canniffe"
|
||||
email: "ocanniffe3@feedburner.test"
|
||||
gender: 'male'
|
||||
conversations:
|
||||
- channel: Channel::Whatsapp
|
||||
source_id: "123456723"
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I'm having trouble connecting to the internet.
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: I'm sorry to hear that. Have you tried restarting your modem/router? If that doesn't work, please let me know and I can provide further assistance.
|
||||
- name: "Viviene Corp"
|
||||
email: "vcorp4@instagram.test"
|
||||
gender: 'female'
|
||||
conversations:
|
||||
- channel: Channel::Sms
|
||||
source_id: "+1234567"
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I'm having trouble with the mobile app. It keeps crashing.
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: I'm sorry to hear that. Can you please try uninstalling and reinstalling the app and see if that helps? If not, please let me know and I can look into it further.
|
||||
- name: "Drake Pittway"
|
||||
email: "dpittway5@chron.test"
|
||||
gender: 'male'
|
||||
conversations:
|
||||
- channel: Channel::Line
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I'm trying to update my account information but it won't save.
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: Sorry for the inconvenience. Can you please provide me with the specific information you're trying to update and the error message you're receiving?
|
||||
- name: "Klaus Crawley"
|
||||
email: "kcrawley6@narod.ru"
|
||||
gender: 'male'
|
||||
conversations:
|
||||
- channel: Channel::WebWidget
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I need some help setting up my new device.
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: No problem! Can you please tell me the make and model of your device and what specifically you need help with?
|
||||
- name: "Bing Cusworth"
|
||||
email: "bcusworth7@arstechnica.test"
|
||||
gender: 'male'
|
||||
conversations:
|
||||
- channel: Channel::TwitterProfile
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I accidentally placed an order for the wrong item. Can I cancel it?
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: I'm sorry to hear that. Can you please provide me with your order number and I'll see if I can cancel it for you?
|
||||
- name: "Claus Jira"
|
||||
email: "cjira8@comcast.net"
|
||||
gender: 'male'
|
||||
conversations:
|
||||
- channel: Channel::Whatsapp
|
||||
source_id: "12323432"
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I'm having trouble with my email. I can't seem to send or receive any messages.
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: I'm sorry to hear that. Can you please tell me what email client you're using and if you're receiving any error messages?
|
||||
- name: "Quent Dalliston"
|
||||
email: "qdalliston9@zimbio.test"
|
||||
gender: 'male'
|
||||
conversations:
|
||||
- channel: Channel::Whatsapp
|
||||
source_id: "12342234324"
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I need some help resetting my password.
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: Sure! Can you please provide me with your username or email address and I'll send you a password reset link?
|
||||
- name: "Coreen Mewett"
|
||||
email: "cmewetta@home.pl"
|
||||
gender: 'female'
|
||||
conversations:
|
||||
- channel: Channel::FacebookPage
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I think someone may have hacked into my account. What should I do?
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: I'm sorry to hear that. Please change your password immediately and enable two-factor authentication if you haven't already done so. I can also assist you in reviewing your account activity if needed.
|
||||
- name: "Benyamin Janeway"
|
||||
email: "bjanewayb@ustream.tv"
|
||||
gender: 'male'
|
||||
conversations:
|
||||
- channel: Channel::Line
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I have a question about your product features.
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: Sure thing! What specific feature are you interested in learning more about?
|
||||
- name: "Cordell Dalinder"
|
||||
email: "cdalinderc@msn.test"
|
||||
gender: 'male'
|
||||
conversations:
|
||||
- channel: Channel::Email
|
||||
source_id: "cdalinderc@msn.test"
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I need help setting up my new printer.
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: No problem! Can you please provide me with the make and model of your printer and what type of device you'll be connecting it to?
|
||||
- name: "Merrile Petruk"
|
||||
email: "mpetrukd@wunderground.test"
|
||||
gender: 'female'
|
||||
conversations:
|
||||
- channel: Channel::Email
|
||||
source_id: "mpetrukd@wunderground.test"
|
||||
priority: urgent
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: Hi, I'm having trouble accessing a file that I shared with someone.
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: I'm sorry to hear that. Can you please tell me which file you're having trouble accessing and who you shared it with? I'll do my best to help you regain access.
|
||||
- name: "Nathaniel Vannuchi"
|
||||
email: "nvannuchie@photobucket.test"
|
||||
gender: 'male'
|
||||
conversations:
|
||||
- channel: Channel::FacebookPage
|
||||
priority: high
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: "Hey there,I need some help with billing, my card is not working on the website."
|
||||
- name: "Olia Olenchenko"
|
||||
email: "oolenchenkof@bluehost.test"
|
||||
gender: 'female'
|
||||
conversations:
|
||||
- channel: Channel::WebWidget
|
||||
priority: high
|
||||
assignee: michael_scott@paperlayer.test
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: "Billing section is not working, it throws some error."
|
||||
- name: "Elisabeth Derington"
|
||||
email: "ederingtong@printfriendly.test"
|
||||
gender: 'female'
|
||||
conversations:
|
||||
- channel: Channel::Whatsapp
|
||||
priority: high
|
||||
source_id: "1223423567"
|
||||
labels:
|
||||
- billing
|
||||
- delivery
|
||||
- ops-handover
|
||||
- premium-customer
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: "Hey \n I didn't get the product delivered, but it shows it is delivered to my address. Please check"
|
||||
- name: "Willy Castelot"
|
||||
email: "wcasteloth@exblog.jp"
|
||||
gender: 'male'
|
||||
conversations:
|
||||
- channel: Channel::WebWidget
|
||||
priority: medium
|
||||
labels:
|
||||
- software
|
||||
- ops-handover
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: "Hey there, \n I need some help with the product, my button is not working on the website."
|
||||
- name: "Ophelia Folkard"
|
||||
email: "ofolkardi@taobao.test"
|
||||
gender: 'female'
|
||||
conversations:
|
||||
- channel: Channel::WebWidget
|
||||
priority: low
|
||||
assignee: michael_scott@paperlayer.test
|
||||
labels:
|
||||
- billing
|
||||
- software
|
||||
- lead
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: "Hey, \n My card is not working on your website. Please help"
|
||||
- name: "Candice Matherson"
|
||||
email: "cmathersonj@va.test"
|
||||
gender: 'female'
|
||||
conversations:
|
||||
- channel: Channel::Email
|
||||
priority: urgent
|
||||
source_id: "cmathersonj@va.test"
|
||||
assignee: michael_scott@paperlayer.test
|
||||
labels:
|
||||
- billing
|
||||
- lead
|
||||
messages:
|
||||
- message_type: incoming
|
||||
content: "Hey, \n I'm looking for some help to figure out if it is the right product for me."
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: Welcome to PaperLayer. Our Team will be getting back you shortly.
|
||||
- message_type: outgoing
|
||||
sender: michael_scott@paperlayer.test
|
||||
content: How may i help you ?
|
||||
sender: michael_scott@paperlayer.test
|
||||
Reference in New Issue
Block a user