Skip to content

Commit

Permalink
[4.3] HELP-10237: refactor decision-making for call recording (#6026)
Browse files Browse the repository at this point in the history
* [4.3] HELP-10237: refactor decision-making for call recording

Added tests for kz_endpoint when merging call recording settings to
clarify the behaviour.

Updated documentation to clarify precedence and the duality of account
and endpoint call recording settings.
  • Loading branch information
jamesaimonetti authored and lazedo committed Sep 11, 2019
1 parent 2d2fd6d commit 0d61ae9
Show file tree
Hide file tree
Showing 12 changed files with 531 additions and 199 deletions.
14 changes: 7 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -250,23 +250,23 @@ app_applications:

code_checks:
@if [ -n "$(CHANGED)" ]; then $(ROOT)/scripts/code_checks.bash $(CHANGED) ; else echo "no code changes for code checking" ; fi
@ERL_LIBS=deps/:core/:applications/ $(ROOT)/scripts/no_raw_json.escript
@ERL_LIBS=deps:core:applications $(ROOT)/scripts/no_raw_json.escript $(CHANGED)
@$(ROOT)/scripts/check-spelling.bash
@$(ROOT)/scripts/kz_diaspora.bash
@$(ROOT)/scripts/edocify.escript

apis:
@ERL_LIBS=deps/:core/:applications/ $(ROOT)/scripts/generate-schemas.escript
@ERL_LIBS=deps:core:applications $(ROOT)/scripts/generate-schemas.escript
@$(ROOT)/scripts/format-json.sh $(shell find applications core -wholename '*/schemas/*.json')
@ERL_LIBS=deps/:core/:applications/ $(ROOT)/scripts/generate-api-endpoints.escript
@ERL_LIBS=deps:core:applications $(ROOT)/scripts/generate-api-endpoints.escript
@$(ROOT)/scripts/generate-doc-schemas.sh `egrep -rl '(#+) Schema' core/ applications/ | grep -v '.[h|e]rl'`
@$(ROOT)/scripts/format-json.sh applications/crossbar/priv/api/swagger.json
@$(ROOT)/scripts/format-json.sh $(shell find applications core -wholename '*/api/*.json')
@ERL_LIBS=deps/:core/:applications/ $(ROOT)/scripts/generate-fs-headers-hrl.escript
@ERL_LIBS=deps/:core/:applications/ $(ROOT)/scripts/generate-kzd-builders.escript
@ERL_LIBS=deps:core:applications $(ROOT)/scripts/generate-fs-headers-hrl.escript
@ERL_LIBS=deps:core:applications $(ROOT)/scripts/generate-kzd-builders.escript

schemas:
@ERL_LIBS=deps/:core/:applications/ $(ROOT)/scripts/generate-schemas.escript
@ERL_LIBS=deps:core:applications $(ROOT)/scripts/generate-schemas.escript

DOCS_ROOT=$(ROOT)/doc/mkdocs
docs: docs-validate docs-report docs-setup docs-build
Expand All @@ -293,7 +293,7 @@ docs-serve: docs-setup docs-build
@$(MAKE) -C $(DOCS_ROOT) DOCS_ROOT=$(DOCS_ROOT) docs-serve

fs-headers:
@ERL_LIBS=deps/:core/:applications/ $(ROOT)/scripts/generate-fs-headers-hrl.escript
@ERL_LIBS=deps:core:applications $(ROOT)/scripts/generate-fs-headers-hrl.escript

validate-swagger:
@$(ROOT)/scripts/validate-swagger.sh
Expand Down
163 changes: 60 additions & 103 deletions applications/callflow/src/cf_route_win.erl
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,6 @@
)
).

-define(ACCOUNT_INBOUND_RECORDING(A), [<<"call_recording">>, <<"account">>, <<"inbound">>, A]).
-define(ACCOUNT_OUTBOUND_RECORDING(A), [<<"call_recording">>, <<"account">>, <<"outbound">>, A]).
-define(ENDPOINT_OUTBOUND_RECORDING(A), [<<"call_recording">>, <<"endpoint">>, <<"outbound">>, A]).
-define(ENDPOINT_INBOUND_RECORDING(A), [<<"call_recording">>, <<"endpoint">>, <<"inbound">>, A]).

-define(ACCOUNT_INBOUND_RECORDING_LABEL(A), <<"inbound from ", A/binary, " to account">>).
-define(ACCOUNT_OUTBOUND_RECORDING_LABEL(A), <<"outbound to ", A/binary, " from account">>).
-define(ENDPOINT_OUTBOUND_RECORDING_LABEL(A), <<"outbound to ", A/binary, " from endpoint">>).

-spec execute_callflow(kz_json:object(), kapps_call:call()) ->
kapps_call:call().
execute_callflow(JObj, Call) ->
Expand Down Expand Up @@ -300,113 +291,79 @@ maybe_start_endpoint_metaflow(Call, EndpointId) ->

-spec maybe_start_recording(kapps_call:call()) -> kapps_call:call().
maybe_start_recording(Call) ->
From = kapps_call:inception_type(Call),
To = case kapps_call:kvs_fetch('cf_no_match', Call) of
'true' -> <<"offnet">>;
_ -> <<"onnet">>
end,
Routines = [{fun maybe_start_account_recording/3, From, To}
,{fun maybe_start_endpoint_recording/3, From, To}
FromNetwork = kapps_call:inception_type(Call), % onnet or offnet
ToNetwork = case kapps_call:kvs_fetch('cf_no_match', Call) of
'true' -> <<"offnet">>;
_ -> <<"onnet">>
end,
Routines = [{fun maybe_start_account_recording/3, FromNetwork, ToNetwork}
,{fun maybe_start_endpoint_recording/3, FromNetwork, ToNetwork}
],
kapps_call:exec(Routines, Call).

-spec maybe_start_account_recording(kz_term:ne_binary(), kz_term:ne_binary(), kapps_call:call()) -> kapps_call:call().
maybe_start_account_recording(From, To, Call) ->
maybe_start_account_recording(FromNetwork, ToNetwork, Call) ->
{'ok', Endpoint} = kz_endpoint:get(kapps_call:account_id(Call), Call),
case maybe_start_call_recording(?ACCOUNT_INBOUND_RECORDING(From)
,?ACCOUNT_INBOUND_RECORDING_LABEL(From)
,Endpoint
,Call
)
of
Call ->
case maybe_start_call_recording(?ACCOUNT_OUTBOUND_RECORDING(To)
,?ACCOUNT_OUTBOUND_RECORDING_LABEL(To)
,Endpoint
,Call
)
of
Call -> Call;
NewCall -> kapps_call:set_is_recording('true', NewCall)
end;
NewCall -> kapps_call:set_is_recording('true', NewCall)

case kz_account_recording:maybe_record_inbound(FromNetwork, Endpoint, Call) of
{'true', NewCall} -> NewCall;
'false' ->
case kz_account_recording:maybe_record_outbound(ToNetwork, Endpoint, Call) of
'false' -> Call;
{'true', NewCall} -> NewCall
end
end.

-spec maybe_start_endpoint_recording(kz_term:ne_binary(), ne_binary, kapps_call:call()) -> kapps_call:call().
maybe_start_endpoint_recording(<<"onnet">>, To, Call) ->
DefaultEndpointId = kapps_call:authorizing_id(Call),
EndpointId = kapps_call:kvs_fetch(?RESTRICTED_ENDPOINT_KEY, DefaultEndpointId, Call),
IsCallForward = kapps_call:is_call_forward(Call),
maybe_start_onnet_endpoint_recording(EndpointId, To, IsCallForward, Call);
maybe_start_endpoint_recording(<<"offnet">>, To, Call) ->
DefaultEndpointId = kapps_call:authorizing_id(Call),
EndpointId = kapps_call:kvs_fetch(?RESTRICTED_ENDPOINT_KEY, DefaultEndpointId, Call),
IsCallForward = kapps_call:is_call_forward(Call),
maybe_start_offnet_endpoint_recording(EndpointId, To, IsCallForward, Call).
-spec maybe_start_endpoint_recording(kz_term:ne_binary(), kz_term:ne_binary(), kapps_call:call()) ->
kapps_call:call().
maybe_start_endpoint_recording(<<"onnet">>, ToNetwork, Call) ->
EndpointId = get_endpoint_id(Call),

-spec maybe_start_onnet_endpoint_recording(kz_term:api_binary(), kz_term:ne_binary(), boolean(), kapps_call:call()) -> kapps_call:call().
maybe_start_onnet_endpoint_recording('undefined', _To, _IsCallForward, Call) -> Call;
maybe_start_onnet_endpoint_recording(EndpointId, To, 'false', Call) ->
case kz_endpoint:get(EndpointId, Call) of
{'ok', Endpoint} ->
maybe_start_call_recording(?ENDPOINT_OUTBOUND_RECORDING(To)
,?ENDPOINT_OUTBOUND_RECORDING_LABEL(To)
,Endpoint
,Call
);
_ -> Call
IsCallForward = kapps_call:is_call_forward(Call),
case maybe_start_onnet_endpoint_recording(EndpointId, ToNetwork, IsCallForward, Call) of
'false' -> Call;
{'true', NewCall} -> NewCall
end;
maybe_start_onnet_endpoint_recording(EndpointId, _To, 'true', Call) ->
Inception = kapps_call:custom_channel_var(<<"Call-Forward-From">>, Call),
case kz_endpoint:get(EndpointId, Call) of
{'ok', Endpoint} ->
Data = kz_json:get_json_value(?ENDPOINT_INBOUND_RECORDING(Inception), Endpoint),
case Data /= 'undefined'
andalso kz_json:is_true(<<"enabled">>, Data)
of
'false' -> Call;
'true' ->
App = kz_endpoint_recording:record_call_command(kz_doc:id(Endpoint), Inception, Data, Call),
NewActions = kz_json:set_value([<<"Execute-On-Answer">>, <<"Record-Endpoint">>], App, kz_json:new()),
kapps_call:kvs_store('outbound_actions', NewActions, Call)
end;
_ -> Call
maybe_start_endpoint_recording(<<"offnet">>, ToNetwork, Call) ->
EndpointId = get_endpoint_id(Call),

IsCallForward = kapps_call:is_call_forward(Call),
case maybe_start_offnet_endpoint_recording(EndpointId, ToNetwork, IsCallForward, Call) of
'false' -> Call;
{'true', NewCall} -> NewCall
end.

-spec maybe_start_offnet_endpoint_recording(kz_term:api_binary(), kz_term:ne_binary(), boolean(), kapps_call:call()) -> kapps_call:call().
maybe_start_offnet_endpoint_recording('undefined', _To, _IsCallForward, Call) -> Call;
maybe_start_offnet_endpoint_recording(_EndpointId, _To, 'false', Call) -> Call;
maybe_start_offnet_endpoint_recording(EndpointId, _To, 'true', Call) ->
Inception = kapps_call:custom_channel_var(<<"Call-Forward-From">>, Call),
-spec maybe_start_onnet_endpoint_recording(kz_term:api_binary(), kz_term:ne_binary(), boolean(), kapps_call:call()) ->
{'true', kapps_call:call()} | 'false'.
maybe_start_onnet_endpoint_recording('undefined', _ToNetwork, _IsCallForward, _Call) -> 'false';
maybe_start_onnet_endpoint_recording(EndpointId, ToNetwork, 'false', Call) ->
case kz_endpoint:get(EndpointId, Call) of
{'ok', Endpoint} ->
Data = kz_json:get_json_value(?ENDPOINT_INBOUND_RECORDING(Inception), Endpoint),
case Data /= 'undefined'
andalso kz_json:is_true(<<"enabled">>, Data)
of
'false' -> Call;
'true' ->
App = kz_endpoint_recording:record_call_command(kz_doc:id(Endpoint), Inception, Data, Call),
NewActions = kz_json:set_value([<<"Execute-On-Answer">>, <<"Record-Endpoint">>], App, kz_json:new()),
kapps_call:kvs_store('outbound_actions', NewActions, Call)
end;
_ -> Call
end.

-spec maybe_start_call_recording(kz_term:ne_binaries(), kz_term:ne_binary(), kz_json:object(), kapps_call:call()) -> kapps_call:call().
maybe_start_call_recording(Key, Label, Endpoint, Call) ->
maybe_start_call_recording(kz_json:get_json_value(Key, Endpoint), Label, Call).

-spec maybe_start_call_recording(kz_term:api_object(), kz_term:ne_binary(), kapps_call:call()) -> kapps_call:call().
maybe_start_call_recording('undefined', _, Call) ->
Call;
maybe_start_call_recording(Data, Label, Call) ->
case kz_json:is_false(<<"enabled">>, Data) of
'true' -> Call;
'false' ->
lager:info("starting call recording by configuration"),
Call1 = kapps_call:kvs_store('recording_follow_transfer', 'false', Call),
kapps_call:start_recording(kz_json:set_value(<<"origin">>, Label, Data), Call1)
kz_endpoint_recording:maybe_record_outbound(ToNetwork, Endpoint, Call);
_ -> 'false'
end;
maybe_start_onnet_endpoint_recording(EndpointId, _ToNetwork, 'true', Call) ->
maybe_start_call_forwarded_recording(EndpointId, Call, kz_endpoint:get(EndpointId, Call)).

%% @doc if the call isn't call-fowarded, and the endpoint is known, kz_endpoint will setup recording
%% on answer
-spec maybe_start_offnet_endpoint_recording(kz_term:api_binary(), kz_term:ne_binary(), boolean(), kapps_call:call()) ->
{'true', kapps_call:call()} | 'false'.
maybe_start_offnet_endpoint_recording('undefined', _ToNetwork, _IsCallForward, _Call) -> 'false';
maybe_start_offnet_endpoint_recording(_EndpointId, _ToNetwork, 'false', _Call) -> 'false';
maybe_start_offnet_endpoint_recording(EndpointId, _ToNetwork, 'true', Call) ->
maybe_start_call_forwarded_recording(EndpointId, Call, kz_endpoint:get(EndpointId, Call)).

-spec maybe_start_call_forwarded_recording(kz_term:ne_binary(), kapps_call:call(), {'ok', kz_json:object()} | {'error', any()}) ->
{'true', kapps_call:call()} | 'false'.
maybe_start_call_forwarded_recording(_EndpointId, _Call, {'error', _E}) -> 'false';
maybe_start_call_forwarded_recording(_EndpointId, Call, {'ok', Endpoint}) ->
FromNetwork = kapps_call:custom_channel_var(<<"Call-Forward-From">>, Call),
case kz_endpoint_recording:maybe_record_inbound(FromNetwork, Endpoint, Call) of
'false' -> 'false';
{'true', {ActionKey, ActionApp}} ->
NewActions = kz_json:set_value(ActionKey, ActionApp, kz_json:new()),
{'true', kapps_call:kvs_store('outbound_actions', NewActions, Call)}
end.

-spec get_incoming_security(kapps_call:call()) -> kz_term:proplist().
Expand Down
2 changes: 1 addition & 1 deletion core/kazoo_apps/src/kapps_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ get_all_default_kvs(JObj) ->
%% @end
%%------------------------------------------------------------------------------

-spec set_string(config_category(), config_key(), kz_term:text() | binary() | string()) ->
-spec set_string(config_category(), config_key(), kz_term:text()) ->
{'ok', kz_json:object()}.
set_string(Category, Key, Value) ->
set(Category, Key, kz_term:to_binary(Value)).
Expand Down
2 changes: 1 addition & 1 deletion core/kazoo_ast/src/raw_json_usage.erl
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ process_app(App) ->
lists:keysort(1, Raw).

-spec process_module(module()) -> [{module(), list()}].
process_module(Module) ->
process_module(Module) when is_atom(Module) ->
Options = [{'accumulator', ?ACC([], [])}
,{'module', fun maybe_skip_kz_json/2}
,{'after_module', fun print_dot/2}
Expand Down
17 changes: 9 additions & 8 deletions core/kazoo_call/src/kapps_call.erl
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@
,to = ?NO_USER_REALM :: kz_term:ne_binary() %% Result of sip_to_user + @ + sip_to_host
,to_user = ?NO_USER :: kz_term:ne_binary() %% SIP to user
,to_realm = ?NO_REALM :: kz_term:ne_binary() %% SIP to host
,inception :: kz_term:api_binary() %% Origin of the call <<"on-net">> | <<"off-net">>
,inception :: kz_term:api_binary() %% Origin of the call <<"onnet">> | <<"offnet">>
,account_db :: kz_term:api_binary() %% The database name of the account that authorized this call
,account_id :: kz_term:api_binary() %% The account id that authorized this call
,authorizing_id :: kz_term:api_binary() %% The ID of the record that authorized this call
Expand Down Expand Up @@ -215,14 +215,14 @@

-type kapps_helper_function() :: fun((kz_term:api_binary(), call()) -> kz_term:api_binary()).

-define(SPECIAL_VARS, [{<<"Caller-ID-Name">>, #kapps_call.caller_id_name}
,{<<"Caller-ID-Number">>, #kapps_call.caller_id_number}
,{<<"Account-ID">>, #kapps_call.account_id}
,{<<"Owner-ID">>, #kapps_call.owner_id}
,{<<"Fetch-ID">>, #kapps_call.fetch_id}
,{<<"Bridge-ID">>, #kapps_call.bridge_id}
-define(SPECIAL_VARS, [{<<"Account-ID">>, #kapps_call.account_id}
,{<<"Authorizing-ID">>, #kapps_call.authorizing_id}
,{<<"Authorizing-Type">>, #kapps_call.authorizing_type}
,{<<"Bridge-ID">>, #kapps_call.bridge_id}
,{<<"Caller-ID-Name">>, #kapps_call.caller_id_name}
,{<<"Caller-ID-Number">>, #kapps_call.caller_id_number}
,{<<"Fetch-ID">>, #kapps_call.fetch_id}
,{<<"Owner-ID">>, #kapps_call.owner_id}
]).

-spec default_helper_function(Field, call()) -> Field.
Expand Down Expand Up @@ -1490,6 +1490,7 @@ start_recording(Data0, Call) ->
,kz_json:get_ne_binary_value(?RECORDING_ID_KEY, Data)
,RecorderPid
}
,{fun set_is_recording/2, 'true'}
],
exec(Routines, Call);
_Err ->
Expand Down Expand Up @@ -1570,7 +1571,7 @@ get_recordings(Call) ->
Q -> Q
end.

-spec inception_type(call()) -> kz_term:api_binary().
-spec inception_type(call()) -> kz_term:ne_binary().
inception_type(#kapps_call{inception='undefined'}) -> <<"onnet">>;
inception_type(#kapps_call{}) -> <<"offnet">>.

Expand Down
2 changes: 0 additions & 2 deletions core/kazoo_endpoint/src/kazoo_endpoint.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,5 @@
-define(ATTR_LOWER_KEY, <<109,108,112,112>>).
-define(ATTR_UPPER_KEY, <<109,097,120,095,112,114,101,099,101,100,101,110,099,101>>).

-define(ENDPOINT_INBOUND_RECORDING(A), [<<"call_recording">>, <<"endpoint">>, <<"inbound">>, A]).

-define(KAZOO_ENDPOINT_HRL, 'true').
-endif.
56 changes: 56 additions & 0 deletions core/kazoo_endpoint/src/kz_account_recording.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
%%%-----------------------------------------------------------------------------
%%% @copyright (C) 2011-2019, 2600Hz
%%% @doc
%%% @end
%%%-----------------------------------------------------------------------------
-module(kz_account_recording).

-export([maybe_record_inbound/3
,maybe_record_outbound/3
]).

-include("kazoo_endpoint.hrl").

-define(ACCOUNT_INBOUND_RECORDING(Network), [<<"call_recording">>, <<"account">>, <<"inbound">>, Network]).
-define(ACCOUNT_OUTBOUND_RECORDING(Network), [<<"call_recording">>, <<"account">>, <<"outbound">>, Network]).

-define(ACCOUNT_INBOUND_RECORDING_LABEL(Network), <<"inbound from ", Network/binary, " to account">>).
-define(ACCOUNT_OUTBOUND_RECORDING_LABEL(Network), <<"outbound to ", Network/binary, " from account">>).

%% @doc should recording be started on call TO the endpoint
-spec maybe_record_inbound(kz_term:ne_binary(), kz_json:object(), kapps_call:call()) ->
{'true', kapps_call:call()} | 'false'.
maybe_record_inbound(FromNetwork, Endpoint, Call) ->
maybe_record_inbound(FromNetwork, Endpoint, Call, kz_json:get_json_value(?ACCOUNT_INBOUND_RECORDING(FromNetwork), Endpoint)).

-spec maybe_record_inbound(kz_term:ne_binary(), kz_json:object(), kapps_call:call(), kz_term:api_object()) ->
{'true', kapps_call:call()} | 'false'.
maybe_record_inbound(_FromNetwork, _Endpoint, _Call, 'undefined') -> 'false';
maybe_record_inbound(FromNetwork, _Endpoint, Call, Data) ->
case kz_json:is_true(<<"enabled">>, Data) of
'false' -> 'false';
'true' ->
LabeledData = kz_json:set_value(<<"origin">>, ?ACCOUNT_INBOUND_RECORDING_LABEL(FromNetwork), Data),
{'true'
,kapps_call:start_recording(LabeledData, kapps_call:kvs_store('recording_follow_transfer', 'false', Call))
}
end.

%% @doc maybe start recording on call made FROM the endpoint
-spec maybe_record_outbound(kz_term:ne_binary(), kz_json:object(), kapps_call:call()) ->
{'true', kapps_call:call()} | 'false'.
maybe_record_outbound(ToNetwork, Endpoint, Call) ->
maybe_record_outbound(ToNetwork, Endpoint, Call, kz_json:get_json_value(?ACCOUNT_OUTBOUND_RECORDING(ToNetwork), Endpoint)).

-spec maybe_record_outbound(kz_term:ne_binary(), kz_json:object(), kapps_call:call(), kz_term:api_object()) ->
{'true', kapps_call:call()} | 'false'.
maybe_record_outbound(_ToNetwork, _Endpoint, _Call, 'undefined') -> 'false';
maybe_record_outbound(ToNetwork, _Endpoint, Call, Data) ->
case kz_json:is_true(<<"enabled">>, Data) of
'false' -> 'false';
'true' ->
LabeledData = kz_json:set_value(<<"origin">>, ?ACCOUNT_OUTBOUND_RECORDING_LABEL(ToNetwork), Data),
{'true'
,kapps_call:start_recording(LabeledData, kapps_call:kvs_store('recording_follow_transfer', 'false', Call))
}
end.
Loading

0 comments on commit 0d61ae9

Please sign in to comment.