Skip to content
This repository has been archived by the owner on Mar 27, 2023. It is now read-only.

Commit

Permalink
Merge branch 'call-tool.caller-ids' into development
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrei committed May 29, 2017
2 parents 90ccc0d + 4f96826 commit 5e33ea2
Show file tree
Hide file tree
Showing 34 changed files with 293 additions and 137 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ gem 'liquid'
gem 'money'
gem 'omniauth-google-oauth2'
gem 'pg'
gem 'phony'
gem 'phony_rails'
gem 'rack-cors', require: 'rack/cors'
gem 'rails', '4.2.8'
gem 'rails-i18n', '~> 4.0.0'
Expand Down
7 changes: 5 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,10 @@ GEM
parser (2.4.0.0)
ast (~> 2.2)
pg (0.18.4)
phony (2.15.40)
phony (2.15.42)
phony_rails (0.14.5)
activesupport (>= 3.0)
phony (~> 2.15)
poltergeist (1.11.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
Expand Down Expand Up @@ -592,7 +595,7 @@ DEPENDENCIES
paper_trail
paperclip (~> 5.0.0)
pg
phony
phony_rails
poltergeist
puma (~> 2.15.3)
rack-cors
Expand Down
4 changes: 1 addition & 3 deletions app/admin/call.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
call.target.try(:phone_number)
end

column :target_call_status do |call|
call.log['CallStatus']
end
column :target_call_status

column :member_call_status do |call|
CallTool::CallStatus.for(call)
Expand Down
6 changes: 6 additions & 0 deletions app/admin/phone_number.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true
ActiveAdmin.register PhoneNumber do
actions :all

permit_params :number, :country
end
4 changes: 3 additions & 1 deletion app/controllers/plugins/call_tools_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ def delete_sound_clip
private

def update_params
params.require(:plugins_call_tool).permit(:targets_csv_file, :active, :title, :sound_clip, :description)
params.require(:plugins_call_tool).permit(
:targets_csv_file, :active, :title, :sound_clip, :description, :caller_phone_number_id
)
end

def targets_params
Expand Down
10 changes: 10 additions & 0 deletions app/helpers/campaigner_facing_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module CampaignerFacingHelper
def format_phone_number(number)
number = number.to_s
if Phony.plausible?(number)
Phony.format(number)
else
number
end
end
end
17 changes: 11 additions & 6 deletions app/models/call.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# twilio_error_code :integer
# target :json
# status :integer default(0)
# action_id :integer
#

class Call < ActiveRecord::Base
Expand All @@ -33,10 +34,6 @@ class Call < ActiveRecord::Base

scope :not_failed, -> { where.not(status: statuses['failed']) }

def target_phone_number
target.phone_number
end

def target_id=(id)
self.target = call_tool.find_target(id)
end
Expand All @@ -56,12 +53,20 @@ def target_call_status
target_call_info['DialCallStatus'] || 'unknown'
end

private

def call_tool
@call_tool ||= Plugins::CallTool.find_by_page_id!(page.id)
end

def caller_id
if target&.caller_id.present?
target.caller_id
else
call_tool.caller_phone_number&.number
end
end

private

def member_phone_number_is_valid
return if member_phone_number.blank?
valid_characters = (/\A[0-9\-\+\(\) \.]+\z/i =~ member_phone_number).present?
Expand Down
16 changes: 13 additions & 3 deletions app/models/call_tool/target.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
# frozen_string_literal: true
class CallTool::Target
include ActiveModel::Model
extend HasPhoneNumber

attr_accessor :country_code, :postal_code, :phone_number, :name, :title
attr_accessor :country_code,
:phone_number,
:phone_extension,
:name,
:title,
:caller_id

validate :country_is_valid
validates :phone_number, presence: true
validates :name, presence: true

validate_phone_number :phone_number, :caller_id
normalize_phone_number :phone_number, :caller_id

def to_hash
{
country_code: country_code,
postal_code: postal_code,
phone_number: phone_number,
phone_extension: phone_extension,
name: name,
title: title
title: title,
caller_id: caller_id
}
end

Expand Down
13 changes: 13 additions & 0 deletions app/models/phone_number.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# == Schema Information
#
# Table name: phone_numbers
#
# id :integer not null, primary key
# number :string
# country :string
#

class PhoneNumber < ActiveRecord::Base
validates :number, presence: true, phony_plausible: true
phony_normalize :number
end
42 changes: 22 additions & 20 deletions app/models/plugins/call_tool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,35 @@
#
# Table name: plugins_call_tools
#
# id :integer not null, primary key
# page_id :integer
# active :boolean
# ref :string
# created_at :datetime
# updated_at :datetime
# title :string
# targets :json is an Array
# sound_clip_file_name :string
# sound_clip_content_type :string
# sound_clip_file_size :integer
# sound_clip_updated_at :datetime
# description :text
# target_by_country :boolean default(TRUE)
# menu_sound_clip_file_name :string
# menu_sound_clip_content_type :string
# menu_sound_clip_file_size :integer
# menu_sound_clip_updated_at :datetime
# restricted_country_code :string
# allow_manual_target_selection :boolean default(FALSE)
# id :integer not null, primary key
# page_id :integer
# active :boolean
# ref :string
# created_at :datetime
# updated_at :datetime
# title :string
# targets :json is an Array
# sound_clip_file_name :string
# sound_clip_content_type :string
# sound_clip_file_size :integer
# sound_clip_updated_at :datetime
# description :text
# target_by_country :boolean default(TRUE)
# menu_sound_clip_file_name :string
# menu_sound_clip_content_type :string
# menu_sound_clip_file_size :integer
# menu_sound_clip_updated_at :datetime
# restricted_country_code :string
# allow_manual_target_selection :boolean default(FALSE)
# caller_phone_number_id :integer
#

class Plugins::CallTool < ActiveRecord::Base
DEFAULTS = {}.freeze

belongs_to :page, touch: true
belongs_to :form
belongs_to :caller_phone_number, class_name: 'PhoneNumber'

has_attached_file :sound_clip, default_url: ''
validates_attachment_content_type :sound_clip, content_type: %r{\Aaudio/.*\Z}, allow_nil: true
Expand Down
31 changes: 22 additions & 9 deletions app/services/call_creator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ def run
member_id: @params[:member_id],
member_phone_number: @params[:member_phone_number],
target_id: @params[:target_id])
Call.transaction do
place_call if @call.save
end

validate_target
validate_call_tool

if errors.blank?
Call.transaction do
place_call if @call.save
end
end

errors.blank?
end
Expand All @@ -45,7 +49,7 @@ def sanitize_params!
def place_call
client = Twilio::REST::Client.new.account.calls
client.create(
from: Settings.calls.default_caller_id,
from: @call.caller_id,
to: @call.member_phone_number,
url: call_start_url(@call),
status_callback: member_call_event_url(@call),
Expand All @@ -61,12 +65,10 @@ def place_call
# 21214: 'To' phone number cannot be reached
@call.update!(twilio_error_code: e.code, status: 'failed')
if (e.code >= 13_223 && e.code <= 13_226) || [21_211, 21_214].include?(e.code)
@errors[:member_phone_number] ||= []
@errors[:member_phone_number] << I18n.t('call_tool.errors.phone_number.cant_connect')
add_error(:member_phone_number, I18n.t('call_tool.errors.phone_number.cant_connect'))
else
Rails.logger.error("Twilio Error: API responded with code #{e.code} for #{@call.attributes.inspect}")
@errors[:base] ||= []
@errors[:base] << I18n.t('call_tool.errors.unknown')
add_error(:base, I18n.t('call_tool.errors.unknown'))
end
end

Expand All @@ -75,7 +77,18 @@ def place_call
# This validation checks for this edge case.
def validate_target
if @call.target.blank? && @params[:target_id].present?
@errors[:base] = [I18n.t('call_tool.errors.target.outdated')]
add_error(:base, I18n.t('call_tool.errors.target.outdated'))
end
end

def validate_call_tool
if @call.caller_id.blank?
add_error(:base, 'Please configure a Caller ID before trying to use the call tool')
end
end

def add_error(key, message)
@errors[key] ||= []
@errors[key] << message
end
end
9 changes: 5 additions & 4 deletions app/services/call_tool/targets_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ class CallTool::TargetsParser

class << self
def parse_csv(csv_string)
# Headers ["country", "postal", "target phone", "target name", "target title"];
# Headers ["country", "target phone", "target phone extension", "target name", "target title", "caller id"];
targets = []
previous_target = CallTool::Target.new

CSV.parse(csv_string, CSV_OPTIONS) do |row|
params = {
country_name: new_target_value(previous_target.country_name, row.field(0)),
postal_code: new_target_value(previous_target.postal_code, row.field(1)),
phone_number: new_target_value(previous_target.phone_number, row.field(2)),
phone_number: new_target_value(previous_target.phone_number, row.field(1)),
phone_extension: new_target_value(previous_target.phone_extension, row.field(2)),
name: new_target_value(previous_target.name, row.field(3)),
title: new_target_value(previous_target.title, row.field(4))
title: new_target_value(previous_target.title, row.field(4)),
caller_id: new_target_value(previous_target.caller_id, row.field(5))
}

new_target = CallTool::Target.new(params)
Expand Down
11 changes: 5 additions & 6 deletions app/services/call_tool/twiml_generator/connect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ module CallTool::TwimlGenerator
class Connect < Base
def run
Twilio::TwiML::Response.new do |r|
r.Dial action: target_call_status_url(call) do |dial|
dial.Number(*number_params)
r.Dial action: target_call_status_url(call), callerId: call.caller_id do |dial|
dial.Number(call.target.phone_number, dial_options)
end
end.text
end

private

def number_params
number = call.target_phone_number.split('ext')
options = number.length > 1 ? { sendDigits: number[1] } : {}
[number[0], options]
def dial_options
extension = call.target.phone_extension
extension.present? ? { sendDigits: extension } : {}
end
end
end
13 changes: 12 additions & 1 deletion app/views/plugins/call_tools/_form.slim
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,20 @@
.wysiwyg id='call-tool-wysiwyg'
= f.hidden_field :description, id: "call-tool-wysiwyg_content"

.form-group
= label_with_tooltip(f, :caller_phone_number_id, t('plugins.call_tool.caller_id'), t('tooltips.call_tool.caller_id'))
= f.select :caller_phone_number_id,
PhoneNumber.all.map { |p| ["#{p.country} #{p.number}", p.id] },
{include_blank: true},
class: 'form-control restricted_country_code'

.form-group
= label_with_tooltip(f, :restricted_country_code, t('plugins.call_tool.restricted_country_code'), t('tooltips.call_tool.restricted_country_code'))
= f.select :restricted_country_code, ISO3166::Country.all.map {|c| [c.name, c.alpha2]}, { include_blank: true }, class: 'form-control restricted_country_code'
= f.select :restricted_country_code,
ISO3166::Country.all.map {|c| [c.name, c.alpha2]},
{ include_blank: true },
class: 'form-control restricted_country_code'

.form-group
label
= t('plugins.call_tool.allow_manual_target_selection')
Expand Down
8 changes: 5 additions & 3 deletions app/views/plugins/call_tools/_targets.slim
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@
table.table.table-stripped
tr
th = t 'plugins.call_tool.targets.country'
th = t 'plugins.call_tool.targets.postal'
th = t 'plugins.call_tool.targets.name'
th = t 'plugins.call_tool.targets.title'
th = t 'plugins.call_tool.targets.phone_number'
th = t 'plugins.call_tool.targets.phone_extension'
th = t 'plugins.call_tool.targets.caller_id'

- targets.each do |target|
tr
td = target.country_name
td = target.postal_code
td = target.name
td = target.title
td = target.phone_number
td = format_phone_number(target.phone_number)
td = target.phone_extension
td = format_phone_number(target.caller_id)

javascript:
$(function() {
Expand Down
6 changes: 4 additions & 2 deletions config/locales/champaign.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ en:
targets: 'CSV File should must adhere to the specified format. First row will be considered as headers. Each following row must have these fields in order: Country, Postal, Target Phone, Target Name, Target Title. Country, Target Phone and Target Name are required fields'
target_by_country: "When enabled the Call Tool will randomly select a target from the selected country on the dropdown. When disabled a random target is chosen disregarding its country. When checked, targets must have a valid country in the Country column in the uploaded CSV."
restricted_country_code: "If you plan to use the Call tool to call numbers only on a specific country, please select the country name here. Leave blank if you'd like to be able to call multiple countries."
caller_id: "Calls made with the CallTool will be seen as made from this phone number"
oauth:
not_authorised: "You're not authorised to authenticate with that account."

Expand Down Expand Up @@ -326,15 +327,16 @@ en:
no_targets_loaded: 'There are no targets loaded'
show_targets: 'Show Targets'
restricted_country_code: 'Restrict calls to'
caller_id: 'Caller ID'
target_by_country: 'Target by country'
allow_manual_target_selection: "Allow manual target selection"
targets:
country: 'Country'
postal: 'Postal Code'
name: 'Name'
title: 'Title'
phone_number: 'Phone Number'

phone_extension: 'Phone Extension'
caller_id: 'Caller ID'

ak_logs:
index:
Expand Down
Loading

0 comments on commit 5e33ea2

Please sign in to comment.