From 51ac54ad46a872cb19fccffee5e1e36c74b14690 Mon Sep 17 00:00:00 2001 From: Daniel Finke Date: Sun, 31 May 2020 21:59:34 +0000 Subject: [PATCH] PISTON-55: acdc distributed member_connect_win (#6558) - better synchronizes states of acdc_agent_fsm per agent --- applications/acdc/src/acdc_agent_fsm.erl | 151 +++++++++++------- applications/acdc/src/acdc_agent_handler.erl | 9 +- applications/acdc/src/acdc_agent_listener.erl | 2 +- applications/acdc/src/acdc_queue_listener.erl | 4 +- applications/acdc/src/kapi_acdc_agent.erl | 61 +++++++ applications/acdc/src/kapi_acdc_queue.erl | 43 ----- applications/crossbar/priv/api/swagger.json | 115 ++++++------- ...> kapi.acdc_agent.member_connect_win.json} | 9 +- 8 files changed, 232 insertions(+), 162 deletions(-) rename applications/crossbar/priv/couchdb/schemas/{kapi.acdc_queue.member_connect_win.json => kapi.acdc_agent.member_connect_win.json} (83%) diff --git a/applications/acdc/src/acdc_agent_fsm.erl b/applications/acdc/src/acdc_agent_fsm.erl index ebcb747a291..f671b04443c 100644 --- a/applications/acdc/src/acdc_agent_fsm.erl +++ b/applications/acdc/src/acdc_agent_fsm.erl @@ -15,7 +15,7 @@ -export([start_link/2, start_link/3, start_link/4, start_link/5 ,call_event/4 ,member_connect_req/2 - ,member_connect_win/2 + ,member_connect_win/3 ,agent_timeout/2 ,originate_ready/2 ,originate_resp/2, originate_started/2, originate_uuid/2 @@ -134,14 +134,17 @@ member_connect_req(ServerRef, JObj) -> gen_statem:cast(ServerRef, {'member_connect_req', JObj}). %%------------------------------------------------------------------------------ -%% @doc When a queue receives a call and needs an agent, it will send a -%% `member_connect_req'. The agent will respond (if possible) with a -%% `member_connect_resp' payload or ignore the request +%% @doc When an agent has been selected to handle the queue call, each process +%% for the agent will receive a `member_connect_win' event. The event will +%% include a flag of whether the winner is on the current node - if true, the +%% agent process will handle call control. Otherwise, the agent process will +%% just follow along through state transitions. %% @end %%------------------------------------------------------------------------------ --spec member_connect_win(pid(), kz_json:object()) -> 'ok'. -member_connect_win(ServerRef, JObj) -> - gen_statem:cast(ServerRef, {'member_connect_win', JObj}). +-type member_connect_win_node() :: 'same_node' | 'different_node'. +-spec member_connect_win(pid(), kz_json:object(), member_connect_win_node()) -> 'ok'. +member_connect_win(ServerRef, JObj, Node) -> + gen_statem:cast(ServerRef, {'member_connect_win', JObj, Node}). -spec agent_timeout(pid(), kz_json:object()) -> 'ok'. agent_timeout(ServerRef, JObj) -> @@ -561,13 +564,12 @@ ready('cast', {'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> {'next_state', 'ready', State}; ready('cast', {'sync_resp', _}, State) -> {'next_state', 'ready', State}; -ready('cast', {'member_connect_win', JObj}, #state{agent_listener=AgentListener - ,endpoints=OrigEPs - ,agent_listener_id=MyId - ,account_id=AccountId - ,agent_id=AgentId - ,connect_failures=CF - }=State) -> +ready('cast', {'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener + ,endpoints=OrigEPs + ,account_id=AccountId + ,agent_id=AgentId + ,connect_failures=CF + }=State) -> Call = kapps_call:from_json(kz_json:get_value(<<"Call">>, JObj)), CallId = kapps_call:call_id(Call), @@ -580,51 +582,80 @@ ready('cast', {'member_connect_win', JObj}, #state{agent_listener=AgentListener CDRUrl = cdr_url(JObj), RecordingUrl = recording_url(JObj), - case kz_json:get_value(<<"Agent-Process-ID">>, JObj) of - MyId -> - lager:debug("trying to ring agent ~s to connect to caller in queue ~s", [AgentId, QueueId]), - - case get_endpoints(OrigEPs, AgentListener, Call, AgentId, QueueId) of - {'error', 'no_endpoints'} -> - lager:info("agent ~s has no endpoints assigned; logging agent out", [AgentId]), - acdc_agent_stats:agent_logged_out(AccountId, AgentId), - agent_logout(self()), - acdc_agent_listener:member_connect_retry(AgentListener, JObj), - {'next_state', 'paused', State}; - {'error', _E} -> - lager:debug("can't take the call, skip me: ~p", [_E]), - acdc_agent_listener:member_connect_retry(AgentListener, JObj), - {'next_state', 'ready', State#state{connect_failures=CF+1}}; - {'ok', UpdatedEPs} -> - acdc_agent_listener:bridge_to_member(AgentListener, Call, JObj, UpdatedEPs, CDRUrl, RecordingUrl), - - CIDName = kapps_call:caller_id_name(Call), - CIDNum = kapps_call:caller_id_number(Call), - - acdc_agent_stats:agent_connecting(AccountId, AgentId, CallId, CIDName, CIDNum, QueueId), - lager:info("trying to ring agent endpoints(~p)", [length(UpdatedEPs)]), - lager:debug("notifications for the queue: ~p", [kz_json:get_value(<<"Notifications">>, JObj)]), - {'next_state', 'ringing', State#state{wrapup_timeout=WrapupTimer - ,member_call=Call - ,member_call_id=CallId - ,member_call_start=kz_time:now() - ,member_call_queue_id=QueueId - ,caller_exit_key=CallerExitKey - ,endpoints=UpdatedEPs - ,queue_notifications=kz_json:get_value(<<"Notifications">>, JObj) - }} - end; - _OtherId -> - lager:debug("monitoring agent ~s to connect to caller in queue ~s", [AgentId, QueueId]), + lager:debug("trying to ring agent ~s to connect to caller in queue ~s", [AgentId, QueueId]), + + case get_endpoints(OrigEPs, AgentListener, Call, AgentId, QueueId) of + {'error', 'no_endpoints'} -> + lager:info("agent ~s has no endpoints assigned; logging agent out", [AgentId]), + acdc_agent_stats:agent_logged_out(AccountId, AgentId), + agent_logout(self()), + acdc_agent_listener:member_connect_retry(AgentListener, JObj), + {'next_state', 'paused', State}; + {'error', _E} -> + lager:debug("can't take the call, skip me: ~p", [_E]), + acdc_agent_listener:member_connect_retry(AgentListener, JObj), + {'next_state', 'ready', State#state{connect_failures=CF+1}}; + {'ok', UpdatedEPs} -> + acdc_util:bind_to_call_events(Call, AgentListener), - acdc_agent_listener:monitor_call(AgentListener, Call, JObj, RecordingUrl), + acdc_agent_listener:bridge_to_member(AgentListener, Call, JObj, UpdatedEPs, CDRUrl, RecordingUrl), + CIDName = kapps_call:caller_id_name(Call), + CIDNum = kapps_call:caller_id_number(Call), + + acdc_agent_stats:agent_connecting(AccountId, AgentId, CallId, CIDName, CIDNum, QueueId), + lager:info("trying to ring agent endpoints(~p)", [length(UpdatedEPs)]), + lager:debug("notifications for the queue: ~p", [kz_json:get_value(<<"Notifications">>, JObj)]), {'next_state', 'ringing', State#state{wrapup_timeout=WrapupTimer + ,member_call=Call + ,member_call_id=CallId + ,member_call_start=kz_time:now() + ,member_call_queue_id=QueueId + ,caller_exit_key=CallerExitKey + ,endpoints=UpdatedEPs + ,queue_notifications=kz_json:get_value(<<"Notifications">>, JObj) + }} + end; +ready('cast', {'member_connect_win', JObj, 'different_node'}, #state{agent_listener=AgentListener + ,endpoints=OrigEPs + ,agent_id=AgentId + ,connect_failures=CF + }=State) -> + Call = kapps_call:from_json(kz_json:get_value(<<"Call">>, JObj)), + CallId = kapps_call:call_id(Call), + + kz_util:put_callid(CallId), + + WrapupTimer = kz_json:get_integer_value(<<"Wrapup-Timeout">>, JObj, 0), + CallerExitKey = kz_json:get_value(<<"Caller-Exit-Key">>, JObj, <<"#">>), + QueueId = kz_json:get_value(<<"Queue-ID">>, JObj), + + CDRUrl = cdr_url(JObj), + RecordingUrl = recording_url(JObj), + + %% Only start monitoring if the agent can actually take the call + case get_endpoints(OrigEPs, AgentListener, Call, AgentId, QueueId) of + {'error', 'no_endpoints'} -> + lager:info("agent ~s has no endpoints assigned; logging agent out", [AgentId]), + {'next_state', 'paused', State}; + {'error', _E} -> + lager:debug("can't take the call, skip me: ~p", [_E]), + {'next_state', 'ready', State#state{connect_failures=CF+1}}; + {'ok', UpdatedEPs} -> + acdc_util:bind_to_call_events(Call, AgentListener), + + acdc_agent_listener:monitor_call(AgentListener, Call, CDRUrl, RecordingUrl), + NextState = 'ringing', + + lager:debug("monitoring agent ~s to connect to caller in queue ~s", [AgentId, QueueId]), + {'next_state', NextState, State#state{wrapup_timeout=WrapupTimer + ,member_call=Call ,member_call_id=CallId ,member_call_start=kz_time:now() ,member_call_queue_id=QueueId ,caller_exit_key=CallerExitKey - ,agent_call_id='undefined' + ,endpoints=UpdatedEPs + ,queue_notifications=kz_json:get_value(<<"Notifications">>, JObj) }} end; ready('cast', {'member_connect_req', _}, #state{max_connect_failures=Max @@ -698,7 +729,7 @@ ready('info', Evt, State) -> -spec ringing(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). ringing('cast', {'member_connect_req', _}, State) -> {'next_state', 'ringing', State}; -ringing('cast', {'member_connect_win', JObj}, #state{agent_listener=AgentListener}=State) -> +ringing('cast', {'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> lager:debug("agent won, but can't process this right now (already ringing)"), acdc_agent_listener:member_connect_retry(AgentListener, JObj), @@ -959,7 +990,7 @@ ringing('info', Evt, State) -> -spec answered(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). answered('cast', {'member_connect_req', _}, State) -> {'next_state', 'answered', State}; -answered('cast', {'member_connect_win', JObj}, #state{agent_listener=AgentListener}=State) -> +answered('cast', {'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> lager:debug("agent won, but can't process this right now (on the phone with someone)"), acdc_agent_listener:member_connect_retry(AgentListener, JObj), @@ -1138,10 +1169,13 @@ wrapup('cast', {'pause', Timeout}, #state{account_id=AccountId {'next_state', 'paused', State#state{pause_ref=Ref}}; wrapup('cast', {'member_connect_req', _}, State) -> {'next_state', 'wrapup', State#state{wrapup_timeout=0}}; -wrapup('cast', {'member_connect_win', JObj}, #state{agent_listener=AgentListener}=State) -> +wrapup('cast', {'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> lager:debug("agent won, but can't process this right now (in wrapup)"), acdc_agent_listener:member_connect_retry(AgentListener, JObj), + {'next_state', 'wrapup', State#state{wrapup_timeout=0}}; +wrapup('cast', {'member_connect_win', _, 'different_node'}, State) -> + lager:debug("received member_connect_win for different node (wrapup)"), {'next_state', 'wrapup', State#state{wrapup_timeout=0}}; wrapup('cast', {'sync_req', JObj}, #state{agent_listener=AgentListener ,wrapup_ref=Ref @@ -1204,7 +1238,7 @@ paused('cast', {'sync_resp', _}, State) -> {'next_state', 'paused', State}; paused('cast', {'member_connect_req', _}, State) -> {'next_state', 'paused', State}; -paused('cast', {'member_connect_win', JObj}, #state{agent_listener=AgentListener}=State) -> +paused('cast', {'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> lager:debug("agent won, but can't process this right now"), acdc_agent_listener:member_connect_retry(AgentListener, JObj), @@ -1251,7 +1285,7 @@ paused('info', Evt, State) -> %% @end %%------------------------------------------------------------------------------ -spec outbound(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). -outbound('cast', {'member_connect_win', JObj}, #state{agent_listener=AgentListener}=State) -> +outbound('cast', {'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> lager:debug("agent won, but can't process this right now (on outbound call)"), acdc_agent_listener:member_connect_retry(AgentListener, JObj), {'next_state', 'outbound', State}; @@ -1416,6 +1450,9 @@ handle_event(Event, StateName, State) -> -spec handle_info(any(), atom(), state()) -> kz_types:handle_fsm_ret(state()). handle_info({'timeout', _Ref, ?SYNC_RESPONSE_MESSAGE}, StateName, State) -> {'next_state', StateName, State}; +handle_info({'member_connect_win', _, 'different_node'}, StateName, State) -> + lager:debug("received member_connect_win for different node (~s)", [StateName]), + {'next_state', StateName, State}; handle_info({'endpoint_edited', EP}, StateName, #state{endpoints=EPs ,account_id=AccountId ,agent_id=AgentId diff --git a/applications/acdc/src/acdc_agent_handler.erl b/applications/acdc/src/acdc_agent_handler.erl index d1046c2f352..69a2e362bbd 100644 --- a/applications/acdc/src/acdc_agent_handler.erl +++ b/applications/acdc/src/acdc_agent_handler.erl @@ -283,8 +283,13 @@ handle_member_message(JObj, Props, <<"connect_req">>) -> 'true' = kapi_acdc_queue:member_connect_req_v(JObj), acdc_agent_fsm:member_connect_req(props:get_value('fsm_pid', Props), JObj); handle_member_message(JObj, Props, <<"connect_win">>) -> - 'true' = kapi_acdc_queue:member_connect_win_v(JObj), - acdc_agent_fsm:member_connect_win(props:get_value('fsm_pid', Props), JObj); + 'true' = kapi_acdc_agent:member_connect_win_v(JObj), + FSMPid = props:get_value('fsm_pid', Props), + MyId = acdc_util:proc_id(FSMPid), + case kz_json:get_value(<<"Agent-Process-ID">>, JObj) of + MyId -> acdc_agent_fsm:member_connect_win(FSMPid, JObj, 'same_node'); + _ -> acdc_agent_fsm:member_connect_win(FSMPid, JObj, 'different_node') + end; handle_member_message(_, _, EvtName) -> lager:debug("not handling member event ~s", [EvtName]). diff --git a/applications/acdc/src/acdc_agent_listener.erl b/applications/acdc/src/acdc_agent_listener.erl index 06d87fec295..50d49601f49 100644 --- a/applications/acdc/src/acdc_agent_listener.erl +++ b/applications/acdc/src/acdc_agent_listener.erl @@ -120,7 +120,7 @@ -define(BINDINGS(AcctId, AgentId), [{'self', []} ,{'acdc_agent', [{'account_id', AcctId} ,{'agent_id', AgentId} - ,{'restrict_to', ['sync', 'stats_req']} + ,{'restrict_to', ['member_connect_win', 'sync', 'stats_req']} ]} ,{'conf', [{'action', <<"*">>} ,{'db', kz_util:format_account_id(AcctId, 'encoded')} diff --git a/applications/acdc/src/acdc_queue_listener.erl b/applications/acdc/src/acdc_queue_listener.erl index 526a62d5a7c..a66fcb436c0 100644 --- a/applications/acdc/src/acdc_queue_listener.erl +++ b/applications/acdc/src/acdc_queue_listener.erl @@ -531,15 +531,15 @@ send_member_connect_req(CallId, AccountId, QueueId, MyQ, MyId) -> -spec send_member_connect_win(kz_json:object(), kapps_call:call(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:proplist()) -> 'ok'. send_member_connect_win(RespJObj, Call, QueueId, MyQ, MyId, QueueOpts) -> CallJSON = kapps_call:to_json(Call), - Q = kz_json:get_value(<<"Server-ID">>, RespJObj), Win = props:filter_undefined( [{<<"Call">>, CallJSON} ,{<<"Process-ID">>, MyId} ,{<<"Agent-Process-ID">>, kz_json:get_value(<<"Agent-Process-ID">>, RespJObj)} ,{<<"Queue-ID">>, QueueId} + ,{<<"Agent-ID">>, kz_json:get_value(<<"Agent-ID">>, RespJObj)} | QueueOpts ++ kz_api:default_headers(MyQ, ?APP_NAME, ?APP_VERSION) ]), - publish(Q, Win, fun kapi_acdc_queue:publish_member_connect_win/2). + publish(Win, fun kapi_acdc_agent:publish_member_connect_win/1). -spec send_agent_timeout(kz_json:object(), kapps_call:call(), kz_term:ne_binary()) -> 'ok'. send_agent_timeout(RespJObj, Call, QueueId) -> diff --git a/applications/acdc/src/kapi_acdc_agent.erl b/applications/acdc/src/kapi_acdc_agent.erl index 7fd90b5053c..ac1f63f57c3 100644 --- a/applications/acdc/src/kapi_acdc_agent.erl +++ b/applications/acdc/src/kapi_acdc_agent.erl @@ -19,6 +19,8 @@ ,logout_queue/1, logout_queue_v/1 ,login_resp/1, login_resp_v/1 + + ,member_connect_win/1, member_connect_win_v/1 ]). -export([bind_q/2 @@ -39,6 +41,8 @@ ,publish_logout_queue/1, publish_logout_queue/2 ,publish_login_resp/2, publish_login_resp/3 + + ,publish_member_connect_win/1, publish_member_connect_win/2 ]). -include_lib("kazoo_stdlib/include/kz_types.hrl"). @@ -380,6 +384,48 @@ login_resp_v(Prop) when is_list(Prop) -> login_resp_v(JObj) -> login_resp_v(kz_json:to_proplist(JObj)). +%%------------------------------------------------------------------------------ +%% Member Connect Win +%%------------------------------------------------------------------------------ +-define(MEMBER_CONNECT_WIN_HEADERS, [<<"Queue-ID">>, <<"Agent-ID">>, <<"Call">>, <<"Agent-Process-ID">>]). +-define(OPTIONAL_MEMBER_CONNECT_WIN_HEADERS, [<<"Ring-Timeout">>, <<"Caller-Exit-Key">> + ,<<"Wrapup-Timeout">>, <<"CDR-Url">> + ,<<"Process-ID">> + ,<<"Record-Caller">>, <<"Recording-URL">> + ,<<"Notifications">> + ]). +-define(MEMBER_CONNECT_WIN_VALUES, [{<<"Event-Category">>, <<"member">>} + ,{<<"Event-Name">>, <<"connect_win">>} + ]). +-define(MEMBER_CONNECT_WIN_TYPES, [{<<"Record-Caller">>, fun kz_term:is_boolean/1}]). + +-spec member_connect_win(kz_term:api_terms()) -> + {'ok', iolist()} | + {'error', string()}. +member_connect_win(Props) when is_list(Props) -> + case member_connect_win_v(Props) of + 'true' -> kz_api:build_message(Props, ?MEMBER_CONNECT_WIN_HEADERS, ?OPTIONAL_MEMBER_CONNECT_WIN_HEADERS); + 'false' -> {'error', "Proplist failed validation for member_connect_win"} + end; +member_connect_win(JObj) -> + member_connect_win(kz_json:to_proplist(JObj)). + +-spec member_connect_win_v(kz_term:api_terms()) -> boolean(). +member_connect_win_v(Prop) when is_list(Prop) -> + kz_api:validate(Prop, ?MEMBER_CONNECT_WIN_HEADERS, ?MEMBER_CONNECT_WIN_VALUES, ?MEMBER_CONNECT_WIN_TYPES); +member_connect_win_v(JObj) -> + member_connect_win_v(kz_json:to_proplist(JObj)). + +-spec member_connect_win_routing_key(kz_term:api_terms() | kz_term:ne_binary()) -> kz_term:ne_binary(). +member_connect_win_routing_key(Props) when is_list(Props) -> + AgentId = props:get_value(<<"Agent-ID">>, Props), + member_connect_win_routing_key(AgentId); +member_connect_win_routing_key(AgentId) when is_binary(AgentId) -> + <<"acdc.member.connect_win.", AgentId/binary>>; +member_connect_win_routing_key(JObj) -> + AgentId = kz_json:get_value(<<"Agent-ID">>, JObj), + member_connect_win_routing_key(AgentId). + %%------------------------------------------------------------------------------ %% Bind/Unbind the queue as appropriate %%------------------------------------------------------------------------------ @@ -397,6 +443,9 @@ bind_q(Q, {AcctId, AgentId, Status}, 'undefined') -> kz_amqp_util:bind_q_to_kapps(Q, sync_req_routing_key(AcctId, AgentId)), kz_amqp_util:bind_q_to_kapps(Q, stats_req_routing_key(AcctId)), kz_amqp_util:bind_q_to_kapps(Q, stats_req_routing_key(AcctId, AgentId)); +bind_q(Q, {_, AgentId, _}=Ids, ['member_connect_win'|T]) -> + kz_amqp_util:bind_q_to_kapps(Q, member_connect_win_routing_key(AgentId)), + bind_q(Q, Ids, T); bind_q(Q, {AcctId, AgentId, Status}=Ids, ['status'|T]) -> kz_amqp_util:bind_q_to_kapps(Q, agent_status_routing_key(AcctId, AgentId, Status)), bind_q(Q, Ids, T); @@ -425,6 +474,9 @@ unbind_q(Q, {AcctId, AgentId, Status}, 'undefined') -> _ = kz_amqp_util:unbind_q_from_kapps(Q, agent_status_routing_key(AcctId, AgentId, Status)), _ = kz_amqp_util:unbind_q_from_kapps(Q, sync_req_routing_key(AcctId, AgentId)), kz_amqp_util:unbind_q_from_kapps(Q, stats_req_routing_key(AcctId)); +unbind_q(Q, {_, AgentId, _}=Ids, ['member_connect_win'|T]) -> + kz_amqp_util:unbind_q_from_kapps(Q, member_connect_win_routing_key(AgentId)), + unbind_q(Q, Ids, T); unbind_q(Q, {AcctId, AgentId, Status}=Ids, ['status'|T]) -> _ = kz_amqp_util:unbind_q_from_kapps(Q, agent_status_routing_key(AcctId, AgentId, Status)), unbind_q(Q, Ids, T); @@ -560,3 +612,12 @@ publish_login_resp(RespQ, JObj) -> publish_login_resp(RespQ, API, ContentType) -> {'ok', Payload} = kz_api:prepare_api_payload(API, ?LOGIN_RESP_VALUES, fun login_resp/1), kz_amqp_util:targeted_publish(RespQ, Payload, ContentType). + +-spec publish_member_connect_win(kz_term:api_terms()) -> 'ok'. +publish_member_connect_win(JObj) -> + publish_member_connect_win(JObj, ?DEFAULT_CONTENT_TYPE). + +-spec publish_member_connect_win(kz_term:api_terms(), kz_term:ne_binary()) -> 'ok'. +publish_member_connect_win(API, ContentType) -> + {'ok', Payload} = kz_api:prepare_api_payload(API, ?MEMBER_CONNECT_WIN_VALUES, fun member_connect_win/1), + kz_amqp_util:kapps_publish(member_connect_win_routing_key(API), Payload, ContentType). diff --git a/applications/acdc/src/kapi_acdc_queue.erl b/applications/acdc/src/kapi_acdc_queue.erl index 5522688de61..782ff7c477e 100644 --- a/applications/acdc/src/kapi_acdc_queue.erl +++ b/applications/acdc/src/kapi_acdc_queue.erl @@ -15,7 +15,6 @@ ,member_call_cancel/1, member_call_cancel_v/1 ,member_connect_req/1, member_connect_req_v/1 ,member_connect_resp/1, member_connect_resp_v/1 - ,member_connect_win/1, member_connect_win_v/1 ,agent_timeout/1, agent_timeout_v/1 ,member_connect_retry/1, member_connect_retry_v/1 ,member_connect_accepted/1, member_connect_accepted_v/1 @@ -45,7 +44,6 @@ ,publish_member_call_cancel/1, publish_member_call_cancel/2 ,publish_member_connect_req/1, publish_member_connect_req/2 ,publish_member_connect_resp/2, publish_member_connect_resp/3 - ,publish_member_connect_win/2, publish_member_connect_win/3 ,publish_agent_timeout/2, publish_agent_timeout/3 ,publish_member_connect_retry/2, publish_member_connect_retry/3 ,publish_member_connect_accepted/2, publish_member_connect_accepted/3 @@ -271,38 +269,6 @@ member_connect_resp_v(Prop) when is_list(Prop) -> member_connect_resp_v(JObj) -> member_connect_resp_v(kz_json:to_proplist(JObj)). -%%------------------------------------------------------------------------------ -%% Member Connect Win -%%------------------------------------------------------------------------------ --define(MEMBER_CONNECT_WIN_HEADERS, [<<"Queue-ID">>, <<"Call">>]). --define(OPTIONAL_MEMBER_CONNECT_WIN_HEADERS, [<<"Ring-Timeout">>, <<"Caller-Exit-Key">> - ,<<"Wrapup-Timeout">>, <<"CDR-Url">> - ,<<"Process-ID">>, <<"Agent-Process-ID">> - ,<<"Record-Caller">>, <<"Recording-URL">> - ,<<"Notifications">> - ]). --define(MEMBER_CONNECT_WIN_VALUES, [{<<"Event-Category">>, <<"member">>} - ,{<<"Event-Name">>, <<"connect_win">>} - ]). --define(MEMBER_CONNECT_WIN_TYPES, [{<<"Record-Caller">>, fun kz_term:is_boolean/1}]). - --spec member_connect_win(kz_term:api_terms()) -> - {'ok', iolist()} | - {'error', string()}. -member_connect_win(Props) when is_list(Props) -> - case member_connect_win_v(Props) of - 'true' -> kz_api:build_message(Props, ?MEMBER_CONNECT_WIN_HEADERS, ?OPTIONAL_MEMBER_CONNECT_WIN_HEADERS); - 'false' -> {'error', "Proplist failed validation for member_connect_win"} - end; -member_connect_win(JObj) -> - member_connect_win(kz_json:to_proplist(JObj)). - --spec member_connect_win_v(kz_term:api_terms()) -> boolean(). -member_connect_win_v(Prop) when is_list(Prop) -> - kz_api:validate(Prop, ?MEMBER_CONNECT_WIN_HEADERS, ?MEMBER_CONNECT_WIN_VALUES, ?MEMBER_CONNECT_WIN_TYPES); -member_connect_win_v(JObj) -> - member_connect_win_v(kz_json:to_proplist(JObj)). - %%------------------------------------------------------------------------------ %% Agent Timeout %%------------------------------------------------------------------------------ @@ -807,15 +773,6 @@ publish_member_connect_resp(Q, API, ContentType) -> {'ok', Payload} = kz_api:prepare_api_payload(API, ?MEMBER_CONNECT_RESP_VALUES, fun member_connect_resp/1), kz_amqp_util:targeted_publish(Q, Payload, ContentType). --spec publish_member_connect_win(kz_term:ne_binary(), kz_term:api_terms()) -> 'ok'. -publish_member_connect_win(Q, JObj) -> - publish_member_connect_win(Q, JObj, ?DEFAULT_CONTENT_TYPE). - --spec publish_member_connect_win(kz_term:ne_binary(), kz_term:api_terms(), kz_term:ne_binary()) -> 'ok'. -publish_member_connect_win(Q, API, ContentType) -> - {'ok', Payload} = kz_api:prepare_api_payload(API, ?MEMBER_CONNECT_WIN_VALUES, fun member_connect_win/1), - kz_amqp_util:targeted_publish(Q, Payload, ContentType). - -spec publish_agent_timeout(kz_term:ne_binary(), kz_term:api_terms()) -> 'ok'. publish_agent_timeout(Q, JObj) -> publish_agent_timeout(Q, JObj, ?DEFAULT_CONTENT_TYPE). diff --git a/applications/crossbar/priv/api/swagger.json b/applications/crossbar/priv/api/swagger.json index 9dcf57022f4..518b8e7e717 100644 --- a/applications/crossbar/priv/api/swagger.json +++ b/applications/crossbar/priv/api/swagger.json @@ -6848,6 +6848,66 @@ ], "type": "object" }, + "kapi.acdc_agent.member_connect_win": { + "description": "AMQP API for acdc_agent.member_connect_win", + "properties": { + "Agent-ID": { + "type": "string" + }, + "Agent-Process-ID": { + "type": "string" + }, + "CDR-Url": { + "type": "string" + }, + "Call": { + "type": "object" + }, + "Caller-Exit-Key": { + "type": "string" + }, + "Event-Category": { + "enum": [ + "member" + ], + "type": "string" + }, + "Event-Name": { + "enum": [ + "connect_win" + ], + "type": "string" + }, + "Notifications": { + "type": "object" + }, + "Process-ID": { + "type": "string" + }, + "Queue-ID": { + "type": "string" + }, + "Record-Caller": { + "type": "boolean" + }, + "Recording-URL": { + "type": "string" + }, + "Ring-Timeout": { + "type": "integer" + }, + "Wrapup-Timeout": { + "type": "integer" + } + }, + "required": [ + "Agent-ID", + "Agent-Process-ID", + "Call", + "Queue-ID" + ], + "type": "object" + }, "kapi.acdc_agent.pause": { "description": "AMQP API for acdc_agent.pause", "properties": { @@ -7441,61 +7501,6 @@ ], "type": "object" }, - "kapi.acdc_queue.member_connect_win": { - "description": "AMQP API for acdc_queue.member_connect_win", - "properties": { - "Agent-Process-ID": { - "type": "string" - }, - "CDR-Url": { - "type": "string" - }, - "Call": { - "type": "object" - }, - "Caller-Exit-Key": { - "type": "string" - }, - "Event-Category": { - "enum": [ - "member" - ], - "type": "string" - }, - "Event-Name": { - "enum": [ - "connect_win" - ], - "type": "string" - }, - "Notifications": { - "type": "object" - }, - "Process-ID": { - "type": "string" - }, - "Queue-ID": { - "type": "string" - }, - "Record-Caller": { - "type": "boolean" - }, - "Recording-URL": { - "type": "string" - }, - "Ring-Timeout": { - "type": "integer" - }, - "Wrapup-Timeout": { - "type": "integer" - } - }, - "required": [ - "Call", - "Queue-ID" - ], - "type": "object" - }, "kapi.acdc_queue.member_hungup": { "description": "AMQP API for acdc_queue.member_hungup", "properties": { diff --git a/applications/crossbar/priv/couchdb/schemas/kapi.acdc_queue.member_connect_win.json b/applications/crossbar/priv/couchdb/schemas/kapi.acdc_agent.member_connect_win.json similarity index 83% rename from applications/crossbar/priv/couchdb/schemas/kapi.acdc_queue.member_connect_win.json rename to applications/crossbar/priv/couchdb/schemas/kapi.acdc_agent.member_connect_win.json index 2de852f056d..58b47c0041d 100644 --- a/applications/crossbar/priv/couchdb/schemas/kapi.acdc_queue.member_connect_win.json +++ b/applications/crossbar/priv/couchdb/schemas/kapi.acdc_agent.member_connect_win.json @@ -1,8 +1,11 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "_id": "kapi.acdc_queue.member_connect_win", - "description": "AMQP API for acdc_queue.member_connect_win", + "_id": "kapi.acdc_agent.member_connect_win", + "description": "AMQP API for acdc_agent.member_connect_win", "properties": { + "Agent-ID": { + "type": "string" + }, "Agent-Process-ID": { "type": "string" }, @@ -50,6 +53,8 @@ } }, "required": [ + "Agent-ID", + "Agent-Process-ID", "Call", "Queue-ID" ],