Browse Source

Merge pull request #4 from bpbp-boop/queues

Add more options for Queues
pull/1/head
Mikhail Rodionov 4 years ago
committed by GitHub
parent
commit
a0a295c227
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 275 additions and 39 deletions
  1. +27
    -2
      src/apps/callflows/i18n/en-US.json
  2. +117
    -31
      src/apps/callflows/submodules/callcenter/callcenter.js
  3. +131
    -6
      src/apps/callflows/submodules/callcenter/views/queue-edit.html

+ 27
- 2
src/apps/callflows/i18n/en-US.json View File

@ -1446,7 +1446,7 @@
"basic": "Basic",
"advanced": "Advanced",
"options": "Options",
"name": "Name",
"namePlaceholder": "Name",
"nameDataContent": "Friendly name for this Queue",
"noName": "no name",
@ -1466,7 +1466,32 @@
"save": "Save",
"thereIsCurrentlyNoUser": "There is currently no user in this queue",
"userLabel": "Users list",
"selectQueue": "Select Queue"
"selectQueue": "Select Queue",
"agent_ring_timeout": "In seconds, how long to ring an agent before progressing to the next agent available",
"agent_wrapup_time": "Pre-defined wait period applied after an agent handles a customer call",
"announce": "Media to play when caller is about to be connected.",
"announcements.interval": "Time between announcements",
"announcements.media.in_the_queue": "Played after the numeric position",
"announcements.media.increase_in_call_volume": "Played if the estimated wait time has increased since the previous wait time announcement",
"announcements.media.the_estimated_wait_time_is": "Played before the estimated wait time media",
"announcements.media.you_are_at_position": "Played before the numeric position",
"announcements.media": "Custom prompts to be played for the announcements",
"announcements.position_announcements_enabled": "Whether announcements of the caller's position in the queue should be played",
"announcements.wait_time_announcements_enabled": "Whether announcements of the estimated wait time in the queue should be played",
"announcements": "Configuration for periodic announcements to callers waiting in the queue",
"caller_exit_key": "Key caller can press while on hold to exit the queue and continue in the callflow",
"cdr_url": "An optional HTTP URL to POST the CDR",
"connection_timeout": "In seconds, how long to try to connect the caller before progressing past the queue callflow action",
"enter_when_empty": "Allows a caller to enter a queue and wait when no agents are available",
"max_priority": "Maximum possible priority level queue will support. Can not be redefined for existing queue.",
"max_queue_size": "How many callers are allowed to wait on hold in the queue (0 for no limit)",
"moh": "Media to play while caller is on hold.",
"name": "A friendly name for the queue",
"record_caller": "When enabled, a caller's audio will be recorded",
"recording_url": "An optional HTTP URL to PUT the call recording after the call ends (and should respond to GET for retrieving the audio data)",
"ring_simultaneously": "The number of agents to try in parallel when connecting a caller",
"strategy": "The queue strategy for connecting agents to callers"
},
"introTitle": "What is Callflow?",
"introDescription": "A callflow defines what happens when someone dials an extension or phone number.",


+ 117
- 31
src/apps/callflows/submodules/callcenter/callcenter.js View File

@ -376,9 +376,8 @@ define(function(require) {
target = args.target || $('#queue-view', parent),
_callbacks = args.callbacks || {},
callbacks = {
save_success: _callbacks.save_success || function(_data) {
save_success: _callbacks.save_success || function (_data) {
self.queueRenderList(parent);
self.queueEdit({
data: {
id: _data.data.id
@ -388,58 +387,85 @@ define(function(require) {
callbacks: callbacks
});
},
save_error: _callbacks.save_error,
delete_success: _callbacks.delete_success || function() {
delete_success: _callbacks.delete_success || function () {
target.empty();
self.queueRenderList(parent);
},
delete_error: _callbacks.delete_error,
after_render: _callbacks.after_render
},
defaults = {
data: $.extend(true, {
connection_timeout: '300',
member_timeout: '5'
/* caller_exit_key: '#' */
}, args.data_defaults || {}),
data: {},
field_data: {
sort_by: {
'first_name': self.i18n.active().callflows.callcenter.first_name,
'last_name': self.i18n.active().callflows.callcenter.last_name
}
}
};
}
self.getUsersList(function(users) {
defaults.field_data.users = users;
monster.parallel({
media_list: function (callback) {
self.callApi({
resource: 'media.list',
data: {
accountId: self.accountId,
filters: {
paginate: false
}
},
success: function (mediaList, status) {
_.each(mediaList.data, function (media) {
if (media.media_source) {
media.name = '[' + media.media_source.substring(0, 3).toUpperCase() + '] ' + media.name;
}
});
if (typeof data === 'object' && data.id) {
self.queueGet(data.id, function(queueData) {
var render_data = $.extend(true, defaults, queueData);
mediaList.data.unshift({
id: '',
name: self.i18n.active().callflows.menu.not_set
});
defaults.field_data.media = mediaList.data;
render_data.field_data.old_list = [];
if ('agents' in queueData.data) {
render_data.field_data.old_list = queueData.data.agents;
callback(null, mediaList);
}
self.queueRender(render_data, target, callbacks);
});
},
user_list: function (callback) {
self.getUsersList(function (users) {
defaults.field_data.users = users;
if (typeof (callbacks.after_render) === 'function') {
callbacks.after_render();
if (typeof data === 'object' && data.id) {
self.queueGet(data.id, function (queueData) {
var render_data = $.extend(true, defaults, queueData);
render_data.field_data.old_list = [];
if ('agents' in queueData.data) {
render_data.field_data.old_list = queueData.data.agents;
}
callback(null, {});
});
}
});
} else {
self.queueRender(defaults, target, callbacks);
}
}, function (err, results) {
let render_data = defaults;
if (typeof data === 'object' && data.id) {
render_data = $.extend(true, defaults, results.user_list);
}
if (typeof (callbacks.after_render) === 'function') {
callbacks.after_render();
}
self.queueRender(render_data, target, callbacks);
if (typeof (callbacks.after_render) === 'function') {
callbacks.after_render();
}
});
},
@ -536,8 +562,13 @@ define(function(require) {
rules: self.validationRules
});
monster.ui.tooltips(queue_html, {
selector: '[rel=popover]'
// monster.ui.tooltips(queue_html, {
// selector: '[rel=popover]'
// });
$('*[rel=popover]', queue_html).popover({
trigger: 'focus',
placement: 'right'
});
$('.queue-save', queue_html).click(function(ev) {
@ -635,6 +666,12 @@ define(function(require) {
}
});
self.queueBindEvents({
data: data,
template: queue_html,
callbacks: callbacks
});
target.empty().append(queue_html);
},
@ -800,7 +837,56 @@ define(function(require) {
normalizeData: function(form_data) {
delete form_data.user_id;
// remove blank fields and let Kazoo set the defaults
$.each(form_data, function(key, value){
if (value === "" || value === null){
delete form_data[key];
}
});
console.log(form_data)
return form_data;
},
queueBindEvents: function (args) {
var self = this,
data = args.data,
callbacks = args.callbacks,
queue_html = args.template;
console.log($('.inline_action_media', queue_html));
$('.inline_action_media', queue_html).click(function (ev) {
var _data = ($(this).data('action') === 'edit') ? {id: $('#announce', queue_html).val()} : {},
_id = _data.id;
ev.preventDefault();
monster.pub('callflows.media.editPopup', {
data: _data,
callback: function (media) {
/* Create */
if (!_id) {
$('#announce', queue_html).append('<option id="' + media.id + '" value="' + media.id + '">' + media.name + '</option>');
$('#announce', queue_html).val(media.id);
$('#edit_link_media', queue_html).show();
} else {
/* Update */
if (media.hasOwnProperty('id')) {
$('#announce #' + media.id, queue_html).text(media.name);
/* Delete */
} else {
$('#announce #' + _id, queue_html).remove();
$('#edit_link_media', queue_html).hide();
}
}
}
});
});
}
};


+ 131
- 6
src/apps/callflows/submodules/callcenter/views/queue-edit.html View File

@ -7,26 +7,151 @@
<form id="queue-form" action="" method="post" class="form-horizontal">
<div class="control-group">
<label class="control-label" for="name">{{ i18nApp.name }}</label>
<label class="control-label" for="name">Name</label>
<div class="controls">
<input id="name" name="name" type="text" data-placement="top" data-toggle="tooltip" placeholder="{{ i18nApp.namePlaceholder }}" value="{{ data.name}}" rel="popover" data-original-title="{{this.help}}" data-content="{{ i18nApp.nameDataContent }}" />
<input id="name" name="name" type="text" data-placement="top" data-toggle="tooltip" value="{{ data.name }}" rel="popover" data-content="{{ i18nApp.name }}" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="connection_timeout">{{ i18nApp.queueTimeout }}</label>
<label class="control-label" for="moh">Music on Hold</label>
<div class="controls">
<input id="connection_timeout" name="connection_timeout" type="text" placeholder="{{ i18nApp.queueTimeoutPlaceholder }}" value="{{ data.connection_timeout }}" rel="popover" data-content="{{ i18nApp.queueTimeoutDataContent }}"/>
<select name="moh" id="moh" class="medium">
{{#select data.moh}}
{{#each field_data.media}}
<option value="{{ id }}">{{ name }}</option>
{{/each}}
{{/select}}
</select>
<div class="edit_create" style="display: inline; margin-left: 20px">
<a style="margin-right: 15px;" class="inline_action_media" data-action="edit" href="#">{{ i18n.callflows.device.edit }}</a>
<a class="inline_action_media" data-action="create" href="#">{{ i18n.callflows.device.create }}</a>
</div>
<span class="help-block">{{ i18nApp.moh }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="announce">Announcement</label>
<div class="controls">
<select name="announce" id="announce" class="medium">
{{#select data.announce}}
{{#each field_data.media}}
<option value="{{ id }}">{{ name }}</option>
{{/each}}
{{/select}}
</select>
<div class="edit_create" style="display: inline; margin-left: 20px">
<a style="margin-right: 15px;" class="inline_action_media" data-action="edit" href="#">{{ i18n.callflows.device.edit }}</a>
<a class="inline_action_media" data-action="create" href="#">{{ i18n.callflows.device.create }}</a>
</div>
<span class="help-block">{{ i18nApp.announce }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="connection_timeout">Connection Timeout</label>
<div class="controls">
<input id="connection_timeout" name="connection_timeout" type="text" value="{{ data.connection_timeout }}" rel="popover" data-content="{{ i18nApp.connection_timeout }}" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="strategy">Ring Strategy</label>
<div class="controls">
<select name="strategy" id="strategy" class="input-medium" >
<option value="round_robin" {{#compare data.strategy "===" "round_robin"}} selected{{/compare}}>Round Robin</option>
<option value="most_idle" {{#compare data.strategy "===" "most_idle"}} selected{{/compare}}>Most Idle</option>
</select>
<span class="help-block">{{ i18nApp.strategy }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="ring_simultaneously">Agents to ring simultaneously</label>
<div class="controls">
<input id="ring_simultaneously" class="span1" name="ring_simultaneously" type="number" value="{{ data.ring_simultaneously }}" rel="popover" data-content="{{ i18nApp.ring_simultaneously }}" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="agent_ring_timeout">Agent Ring Timeout</label>
<div class="controls">
<input id="agent_ring_timeout" name="agent_ring_timeout" type="text" value="{{ data.agent_ring_timeout }}" rel="popover" data-content="{{ i18nApp.agent_ring_timeout }}" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="agent_ring_timeout">Agent Wrap-up Time</label>
<div class="controls">
<input id="agent_wrapup_time" name="agent_wrapup_time" type="text" value="{{ data.agent_wrapup_time }}" rel="popover" data-content="{{ i18nApp.agent_wrapup_time }}" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="enter_when_empty">Enter When Empty</label>
<div class="controls">
<select name="enter_when_empty" id="enter_when_empty" class="input-medium" >
<option value=""></option>
<option value="true" {{#if data.enter_when_empty}} selected{{/if}}>Yes</option>
<option value="false" {{#compare data.enter_when_empty "===" false}} selected{{/compare}}>No</option>
</select>
<span class="help-block">{{ i18nApp.enter_when_empty }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="member_timeout">{{ i18nApp.memberTimeout }}</label>
<label class="control-label" for="record_caller">Record Caller</label>
<div class="controls">
<input id="member_timeout" name="member_timeout" type="text" placeholder="{{ i18nApp.memberTimeoutPlaceholder }}" value="{{ data.member_timeout }}" rel="popover" data-content="{{ i18nApp.memberTimeoutDataContent }}"/>
<select name="record_caller" id="record_caller" class="input-medium" >
<option value=""></option>
<option value="true" {{#if data.record_caller}} selected{{/if}}>Yes</option>
<option value="false" {{#compare data.record_caller "===" false}} selected{{/compare}}>No</option>
</select>
<span class="help-block">{{ i18nApp.record_caller }}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="recording_url">Recording URL</label>
<div class="controls">
<input id="recording_url" name="recording_url" type="text" value="{{ data.recording_url }}" rel="popover" data-content="{{ i18nApp.recording_url }}" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="cdr_url">CDR URL</label>
<div class="controls">
<input id="cdr_url" name="cdr_url" type="text" value="{{ data.cdr_url }}" rel="popover" data-content="{{ i18nApp.cdr_url }}" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="caller_exit_key">Caller exit key</label>
<div class="controls">
<select name="caller_exit_key" id="caller_exit_key" class="input-medium" >
<option value=""></option>
<option value="1" {{#compare data.caller_exit_key "===" "1"}} selected{{/compare}}>1</option>
<option value="2" {{#compare data.caller_exit_key "===" "2"}} selected{{/compare}}>2</option>
<option value="3" {{#compare data.caller_exit_key "===" "3"}} selected{{/compare}}>3</option>
<option value="4" {{#compare data.caller_exit_key "===" "4"}} selected{{/compare}}>4</option>
<option value="5" {{#compare data.caller_exit_key "===" "5"}} selected{{/compare}}>5</option>
<option value="6" {{#compare data.caller_exit_key "===" "6"}} selected{{/compare}}>6</option>
<option value="7" {{#compare data.caller_exit_key "===" "7"}} selected{{/compare}}>7</option>
<option value="8" {{#compare data.caller_exit_key "===" "8"}} selected{{/compare}}>8</option>
<option value="9" {{#compare data.caller_exit_key "===" "9"}} selected{{/compare}}>9</option>
<option value="*" {{#compare data.caller_exit_key "===" "*"}} selected{{/compare}}>*</option>
<option value="0" {{#compare data.caller_exit_key "===" "0"}} selected{{/compare}}>0</option>
<option value="#" {{#compare data.caller_exit_key "===" "#"}} selected{{/compare}}>#</option>
</select>
<span class="help-block">{{ i18nApp.caller_exit_key }}</span>
</div>
</div>
<h4>{{ i18nApp.userLabel }}</h4>
<table class="users-table table table-striped">
<thead>


Loading…
Cancel
Save