feat: Ability to seed Demo Accounts (#5352)

Introduces the ability to seed sample data into accounts in development and staging.

fixes: #3429
This commit is contained in:
Sojan Jose 2022-09-01 00:31:43 +05:30 committed by GitHub
parent ebea5428bc
commit c8d01a84ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 593 additions and 76 deletions

View file

@ -184,3 +184,4 @@ AllCops:
- db/migrate/20200927135222_add_last_activity_at_to_conversation.rb - db/migrate/20200927135222_add_last_activity_at_to_conversation.rb
- db/migrate/20210306170117_add_last_activity_at_to_contacts.rb - db/migrate/20210306170117_add_last_activity_at_to_contacts.rb
- db/migrate/20220809104508_revert_cascading_indexes.rb - db/migrate/20220809104508_revert_cascading_indexes.rb

View file

@ -158,6 +158,10 @@ group :test do
gem 'webmock' gem 'webmock'
end end
group :development, :test, :staging do
gem 'faker'
end
group :development, :test do group :development, :test do
gem 'active_record_query_trace' gem 'active_record_query_trace'
##--- gems for debugging and error reporting ---## ##--- gems for debugging and error reporting ---##
@ -167,7 +171,6 @@ group :development, :test do
gem 'byebug', platform: :mri gem 'byebug', platform: :mri
gem 'climate_control' gem 'climate_control'
gem 'factory_bot_rails' gem 'factory_bot_rails'
gem 'faker'
gem 'listen' gem 'listen'
gem 'mock_redis' gem 'mock_redis'
gem 'pry-rails' gem 'pry-rails'

View file

@ -41,4 +41,9 @@ class SuperAdmin::AccountsController < SuperAdmin::ApplicationController
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions # See https://administrate-prototype.herokuapp.com/customizing_controller_actions
# for more information # for more information
def seed
Seeders::AccountSeeder.new(account: requested_resource).perform!
redirect_back(fallback_location: [namespace, requested_resource], notice: 'Account seeding triggered')
end
end end

View file

@ -2,6 +2,8 @@ require 'administrate/field/base'
class AvatarField < Administrate::Field::Base class AvatarField < Administrate::Field::Base
def avatar_url def avatar_url
data.presence&.gsub('?d=404', '?d=mp') return data.presence if data.presence
resource.is_a?(User) ? '/assets/administrate/user/avatar.png' : '/assets/administrate/bot/avatar.png'
end end
end end

View file

@ -0,0 +1,14 @@
<% if !Rails.env.production? %>
<section class="main-content__body">
<hr/>
<%= form_for([:seed, namespace, page.resource], method: :post, html: { class: "form" }) do |f| %>
<div class="form-actions">
<div><p> Click the button to generate seed data into this account for demos.</p>
<p class="text-color-red">Note: This will clear all the existing data in this account.</p>
</div>
<%= f.submit 'Generate Seed Data' %>
</div>
<% end %>
</section>
<% end %>

View file

@ -85,3 +85,5 @@ as well as a link to its edit page.
<% end %> <% end %>
</section> </section>
<%= render partial: "seed_data", locals: {page: page} %>

View file

@ -340,7 +340,9 @@ Rails.application.routes.draw do
resource :app_config, only: [:show, :create] resource :app_config, only: [:show, :create]
# order of resources affect the order of sidebar navigation in super admin # order of resources affect the order of sidebar navigation in super admin
resources :accounts resources :accounts, only: [:index, :new, :create, :show, :edit, :update] do
post :seed, on: :member
end
resources :users, only: [:index, :new, :create, :show, :edit, :update] resources :users, only: [:index, :new, :create, :show, :edit, :update]
resources :access_tokens, only: [:index, :show] resources :access_tokens, only: [:index, :show]
resources :installation_configs, only: [:index, :new, :create, :show, :edit, :update] resources :installation_configs, only: [:index, :new, :create, :show, :edit, :update]

View file

@ -11,6 +11,12 @@ end
## Seeds for Local Development ## Seeds for Local Development
unless Rails.env.production? unless Rails.env.production?
# Enables creating additional accounts from dashboard
installation_config = InstallationConfig.find_by(name: 'CREATE_NEW_ACCOUNT_FROM_DASHBOARD')
installation_config.value = true
installation_config.save!
GlobalConfig.clear_cache
account = Account.create!( account = Account.create!(
name: 'Acme Inc' name: 'Acme Inc'
) )
@ -35,12 +41,6 @@ unless Rails.env.production?
role: :administrator role: :administrator
) )
# Enables creating additional accounts from dashboard
installation_config = InstallationConfig.find_by(name: 'CREATE_NEW_ACCOUNT_FROM_DASHBOARD')
installation_config.value = true
installation_config.save!
GlobalConfig.clear_cache
web_widget = Channel::WebWidget.create!(account: account, website_url: 'https://acme.inc') web_widget = Channel::WebWidget.create!(account: account, website_url: 'https://acme.inc')
inbox = Inbox.create!(channel: web_widget, account: account, name: 'Acme Support') inbox = Inbox.create!(channel: web_widget, account: account, name: 'Acme Support')

View file

@ -1,89 +1,105 @@
## Class to generate sample data for a chatwoot test Account. ## Class to generate sample data for a chatwoot test @Account.
############################################################ ############################################################
### Usage ##### ### Usage #####
# #
# # Seed an account with all data types in this class # # Seed an account with all data types in this class
# Seeders::AccountSeeder.new(account: account).seed! # Seeders::AccountSeeder.new(account: @Account.find(1)).perform!
# #
# # When you want to seed only a specific type of data
# Seeders::AccountSeeder.new(account: account).seed_canned_responses
# # Seed specific number of objects
# Seeders::AccountSeeder.new(account: account).seed_canned_responses(count: 10)
# #
############################################################ ############################################################
class Seeders::AccountSeeder class Seeders::AccountSeeder
pattr_initialize [:account!] def initialize(account:)
raise 'Account Seeding is not allowed in production.' if Rails.env.production?
def seed! @account_data = HashWithIndifferentAccess.new(YAML.safe_load(File.read(Rails.root.join('lib/seeders/seed_data.yml'))))
@account = account
end
def perform!
set_up_account
seed_teams
set_up_users
seed_labels
seed_canned_responses seed_canned_responses
seed_inboxes 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
end
def seed_teams
@account_data['teams'].each do |team_name|
@account.teams.create!(name: team_name)
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 = 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']}")
AccountUser.create_with(role: (user['role'] || 'agent')).find_or_create_by!(account_id: @account.id, user_id: user_record.id)
next if user['team'].blank?
add_user_to_teams(user: user_record, teams: user['team'])
end
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 end
def seed_canned_responses(count: 50) def seed_canned_responses(count: 50)
count.times do count.times do
account.canned_responses.create(content: Faker::Quote.fortune_cookie, short_code: Faker::Alphanumeric.alpha(number: 10)) @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.create!(contact_data.slice('name', 'email'))
Avatar::AvatarFromUrlJob.perform_later(contact, "https://xsgames.co/randomusers/avatar.php?g=#{contact_data['gender']}")
contact_data['conversations'].each do |conversation_data|
inbox = @account.inboxes.find_by(channel_type: conversation_data['channel'])
contact_inbox = inbox.contact_inboxes.create!(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.find_by(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'])
end
def create_messages(conversation:, messages:)
messages.each do |message_data|
sender = User.find_by(email: message_data['sender']) if message_data['sender'].present?
conversation.messages.create!(message_data.slice('content', 'message_type').merge(account: conversation.inbox.account, sender: sender,
inbox: conversation.inbox))
end end
end end
def seed_inboxes def seed_inboxes
seed_website_inbox Seeders::InboxSeeder.new(account: @account, company_data: @account_data[:company]).perform!
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://acme.inc')
Inbox.create!(channel: channel, account: account, name: 'Acme Website')
end
def seed_facebook_inbox
channel = Channel::FacebookPage.create!(account: account, user_access_token: 'test', page_access_token: 'test', page_id: 'test')
Inbox.create!(channel: channel, account: account, name: 'Acme Facebook')
end
def seed_twitter_inbox
channel = Channel::TwitterProfile.create!(account: account, twitter_access_token: 'test', twitter_access_token_secret: 'test', profile_id: '123')
Inbox.create!(channel: channel, account: account, name: 'Acme Twitter')
end
def seed_whatsapp_inbox
channel = Channel::Whatsapp.create!(account: account, phone_number: '+123456789')
Inbox.create!(channel: channel, account: account, name: 'Acme Whatsapp')
end
def seed_sms_inbox
channel = Channel::Sms.create!(account: account, phone_number: '+123456789')
Inbox.create!(channel: channel, account: account, name: 'Acme SMS')
end
def seed_email_inbox
channel = Channel::Email.create!(account: account, email: 'test@acme.inc', forward_to_email: 'test_fwd@acme.inc')
Inbox.create!(channel: channel, account: account, name: 'Acme Email')
end
def seed_api_inbox
channel = Channel::Api.create!(account: account)
Inbox.create!(channel: channel, account: account, name: 'Acme API')
end
def seed_telegram_inbox
# rubocop:disable Rails/SkipsModelValidations
Channel::Telegram.insert({ account_id: account.id, bot_name: 'Acme', bot_token: 'test', created_at: Time.now.utc, updated_at: Time.now.utc },
returning: %w[id])
channel = Channel::Telegram.find_by(bot_token: 'test')
Inbox.create!(channel: channel, account: account, name: 'Acme Telegram')
# rubocop:enable Rails/SkipsModelValidations
end
def seed_line_inbox
channel = Channel::Line.create!(account: account, line_channel_id: 'test', line_channel_secret: 'test', line_channel_token: 'test')
Inbox.create!(channel: channel, account: account, name: 'Acme Line')
end end
end end

105
lib/seeders/inbox_seeder.rb Normal file
View 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.' if 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

367
lib/seeders/seed_data.yml Normal file
View file

@ -0,0 +1,367 @@
company:
name: 'PaperLayer'
domain: 'paperlayer.test'
users:
- name: 'Michael Scott'
gender: male
email: 'michale@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'
- name: 'Danny Cordray'
gender: female
email: 'danny@paperlayer.test'
team:
- 'Sales'
- name: 'Ben Nugent'
gender: male
email: 'ben@paperlayer.test'
team:
- 'Sales'
- name: 'Todd Packer'
gender: male
email: 'todd@paperlayer.test'
team:
- 'Sales'
- name: 'Cathy Simms'
gender: female
email: 'cathy@paperlayer.test'
team:
- 'Administration'
- name: 'Hunter Jo'
gender: male
email: 'hunter@paperlayer.test'
team:
- 'Administration'
- name: 'Rolando Silva'
gender: male
email: 'rolando@paperlayer.test'
team:
- 'Administration'
- name: 'Stephanie Wilson'
gender: female
email: 'stephanie@paperlayer.test'
team:
- 'Administration'
- 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'
- 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'
- name: 'Kendall'
gender: male
email: 'kendall@paperlayer.test'
- email: 'sadiq@paperlayer.test'
name: 'Sadiq'
gender: male
teams:
- '💰 Sales'
- '💼 Management'
- '👩‍💼 Administration'
- '🚛 Warehouse'
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: hello world
- name: "Tiffanie Cloughton"
email: "tcloughton1@newyorker.test"
gender: 'female'
conversations:
- channel: Channel::FacebookPage
messages:
- message_type: incoming
content: hello world
- name: "Melonie Keatch"
email: "mkeatch2@reuters.test"
gender: 'female'
conversations:
- channel: Channel::TwitterProfile
messages:
- message_type: incoming
content: hello world
- name: "Olin Canniffe"
email: "ocanniffe3@feedburner.test"
gender: 'male'
conversations:
- channel: Channel::Whatsapp
messages:
- message_type: incoming
content: hello world
- name: "Viviene Corp"
email: "vcorp4@instagram.test"
gender: 'female'
conversations:
- channel: Channel::Sms
source_id: "+1234567"
messages:
- message_type: incoming
content: hello world
- name: "Drake Pittway"
email: "dpittway5@chron.test"
gender: 'male'
conversations:
- channel: Channel::Line
messages:
- message_type: incoming
content: hello world
- name: "Klaus Crawley"
email: "kcrawley6@narod.ru"
gender: 'male'
conversations:
- channel: Channel::WebWidget
messages:
- message_type: incoming
content: hello world
- name: "Bing Cusworth"
email: "bcusworth7@arstechnica.test"
gender: 'male'
conversations:
- channel: Channel::TwitterProfile
messages:
- message_type: incoming
content: hello world
- name: "Claus Jira"
email: "cjira8@comcast.net"
gender: 'male'
conversations:
- channel: Channel::Whatsapp
messages:
- message_type: incoming
content: hello world
- name: "Quent Dalliston"
email: "qdalliston9@zimbio.test"
gender: 'male'
conversations:
- channel: Channel::Whatsapp
messages:
- message_type: incoming
content: hello world
- name: "Coreen Mewett"
email: "cmewetta@home.pl"
gender: 'female'
conversations:
- channel: Channel::FacebookPage
messages:
- message_type: incoming
content: hello world
- name: "Benyamin Janeway"
email: "bjanewayb@ustream.tv"
gender: 'male'
conversations:
- channel: Channel::Line
messages:
- message_type: incoming
content: hello world
- name: "Cordell Dalinder"
email: "cdalinderc@msn.test"
gender: 'male'
conversations:
- channel: Channel::Email
source_id: "cdalinderc@msn.test"
messages:
- message_type: incoming
content: hello world
- name: "Merrile Petruk"
email: "mpetrukd@wunderground.test"
gender: 'female'
conversations:
- channel: Channel::Email
source_id: "mpetrukd@wunderground.test"
messages:
- message_type: incoming
content: hello world
- name: "Nathaniel Vannuchi"
email: "nvannuchie@photobucket.test"
gender: 'male'
conversations:
- channel: Channel::FacebookPage
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
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
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
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
assignee: michael_scott@paperlayer.test
messages:
- message_type: incoming
content: "Hey, \n My card is not working on your website. Please help"
- name: "Candice Matherson"
email: "cmathersonj@va.gov"
gender: 'female'
conversations:
- channel: Channel::Email
source_id: "cmathersonj@va.gov"
assignee: michael_scott@paperlayer.test
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
content: Welcome to PaperLayer. Our Team will be getting back you shortly.
- message_type: outgoing
content: How may i help you ?
sender: michael_scott@paperlayer.test

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB