diff --git a/Makefile b/Makefile index fc270774c5e..9a3f0f8aa85 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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 diff --git a/applications/callflow/src/cf_route_win.erl b/applications/callflow/src/cf_route_win.erl index 2e6af517916..5785f228f28 100644 --- a/applications/callflow/src/cf_route_win.erl +++ b/applications/callflow/src/cf_route_win.erl @@ -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) -> @@ -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(). diff --git a/core/kazoo_apps/src/kapps_config.erl b/core/kazoo_apps/src/kapps_config.erl index 0e7bd948b0f..f3c2af9ce27 100644 --- a/core/kazoo_apps/src/kapps_config.erl +++ b/core/kazoo_apps/src/kapps_config.erl @@ -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)). diff --git a/core/kazoo_ast/src/raw_json_usage.erl b/core/kazoo_ast/src/raw_json_usage.erl index 819f51e696e..e14fc0646c0 100644 --- a/core/kazoo_ast/src/raw_json_usage.erl +++ b/core/kazoo_ast/src/raw_json_usage.erl @@ -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} diff --git a/core/kazoo_call/src/kapps_call.erl b/core/kazoo_call/src/kapps_call.erl index 6b22db3dfb6..f271f3086d6 100644 --- a/core/kazoo_call/src/kapps_call.erl +++ b/core/kazoo_call/src/kapps_call.erl @@ -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 @@ -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. @@ -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 -> @@ -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">>. diff --git a/core/kazoo_endpoint/src/kazoo_endpoint.hrl b/core/kazoo_endpoint/src/kazoo_endpoint.hrl index 9da2795fe15..e8b9de0f9dd 100644 --- a/core/kazoo_endpoint/src/kazoo_endpoint.hrl +++ b/core/kazoo_endpoint/src/kazoo_endpoint.hrl @@ -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. diff --git a/core/kazoo_endpoint/src/kz_account_recording.erl b/core/kazoo_endpoint/src/kz_account_recording.erl new file mode 100644 index 00000000000..852f640e37e --- /dev/null +++ b/core/kazoo_endpoint/src/kz_account_recording.erl @@ -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. diff --git a/core/kazoo_endpoint/src/kz_endpoint.erl b/core/kazoo_endpoint/src/kz_endpoint.erl index e8f160904c2..0f5cf798787 100644 --- a/core/kazoo_endpoint/src/kz_endpoint.erl +++ b/core/kazoo_endpoint/src/kz_endpoint.erl @@ -19,7 +19,11 @@ ]). -ifdef(TEST). --export([attributes_keys/0]). +-export([attributes_keys/0 + ,merge_attribute/4 + ]). + +-include_lib("eunit/include/eunit.hrl"). -endif. -include("kazoo_endpoint.hrl"). @@ -334,8 +338,8 @@ merge_attribute(<<"record_call">> = Key, Account, Endpoint, Owner) -> kz_json:set_value(Key, Merged, Endpoint); merge_attribute(<<"call_recording">> = Key, Account, Endpoint, Owner) -> AccountAttr = get_account_record_call_properties(Account), - OwnerAttr = get_endpoint_record_call_properties(Owner), - EndpointAttr = get_endpoint_record_call_properties(Endpoint), + OwnerAttr = get_user_record_call_properties(Owner), + EndpointAttr = get_device_record_call_properties(Endpoint), Merged = kz_json:merge([AccountAttr, OwnerAttr, EndpointAttr]), kz_json:set_value(Key, Merged, Endpoint); merge_attribute(<<"outbound_flags">>, Account, Endpoint, Owner) -> @@ -383,65 +387,78 @@ merge_attribute_caller_id_emergency(Attribute, Endpoint, EndpointAttr) -> Value -> kz_json:set_value(Key, Value, Endpoint) end. --spec merge_call_recording(kz_json:object()) -> kz_json:object(). -merge_call_recording(JObj) -> +-spec merge_call_recording(kzd_call_recording:doc()) -> kzd_call_recording:doc(). +merge_call_recording(RecordingSettings) -> AnyDirections = [<<"inbound">>, <<"outbound">>], AnyNets = [<<"onnet">>, <<"offnet">>], - AnyDirection = kz_json:get_json_value(<<"any">>, JObj, kz_json:new()), - F1 = fun(K1, V1) -> merge_call_recording(K1, V1, AnyDirection) end, - JObj1 = lists:foldl(F1, kz_json:delete_key(<<"any">>, JObj), AnyDirections), - F2 = fun(K, V, Acc) -> merge_call_recording(K, V, Acc, AnyNets) end, - kz_json:foldl(F2, JObj1, JObj1). - --spec merge_call_recording(kz_term:ne_binary(), kz_json:object(), kz_json:object()) -> kz_json:object(). -merge_call_recording(K, JObj, ToMerge) -> - case kz_json:get_json_value(K, JObj) of - 'undefined' -> kz_json:set_value(K, ToMerge, JObj); - V -> kz_json:set_value(K, kz_json:merge(ToMerge, V), JObj) - end. - --spec merge_call_recording(kz_term:ne_binary(), kz_json:object(), kz_json:object(), kz_term:ne_binaries()) -> kz_json:object(). -merge_call_recording(K, JObj, Acc, List) -> - Any = kz_json:get_json_value(<<"any">>, JObj, kz_json:from_list([{<<"enabled">>, 'false'}])), - Fun = fun(K1, V1) -> merge_call_recording(K1, V1, Any) end, - kz_json:set_value(K, lists:foldl(Fun, kz_json:delete_key(<<"any">>, JObj), List), Acc). - --spec get_account_record_call_properties(kz_term:api_object()) -> kz_json:object(). -get_account_record_call_properties(JObj) -> - kz_json:foldl(fun(K, V, Acc) -> - kz_json:set_value(K, merge_call_recording(V), Acc) + AnyDirectionSettings = kzd_call_recording:any(RecordingSettings, kz_json:new()), + + F1 = fun(Direction, RSAcc1) -> merge_call_recording_direction(Direction, RSAcc1, AnyDirectionSettings) end, + RecordingSettings1 = lists:foldl(F1, kz_json:delete_key(<<"any">>, RecordingSettings), AnyDirections), + + F2 = fun(Direction, Networks, RSAcc2) -> merge_call_recording_networks(Direction, Networks, RSAcc2, AnyNets) end, + kz_json:foldl(F2, RecordingSettings1, RecordingSettings1). + +-spec merge_call_recording_direction(kz_term:ne_binary(), kzd_call_recording:doc(), kz_json:object()) -> kz_json:object(). +merge_call_recording_direction(Direction, RecordingSettings, AnyDirectionSettings) -> + case kz_json:get_json_value(Direction, RecordingSettings) of + 'undefined' -> kz_json:set_value(Direction, AnyDirectionSettings, RecordingSettings); + V -> kz_json:set_value(Direction, kz_json:merge(AnyDirectionSettings, V), RecordingSettings) + end. + +-spec merge_call_recording_networks(kz_term:ne_binary(), kz_json:object(), kz_json:object(), kz_term:ne_binaries()) -> kz_json:object(). +merge_call_recording_networks(Direction, NetworksJObj, RecordingSettings, AnyNetworks) -> + AnyNetworkSettings = kzd_call_recording:any(NetworksJObj, kz_json:from_list([{<<"enabled">>, 'false'}])), + Fun = fun(Network, NetworksAcc) -> merge_call_recording_direction(Network, NetworksAcc, AnyNetworkSettings) end, + UpdatedNetworks = lists:foldl(Fun, kz_json:delete_key(<<"any">>, NetworksJObj), AnyNetworks), + kz_json:set_value(Direction, UpdatedNetworks, RecordingSettings). + +-spec get_account_record_call_properties(kzd_accounts:doc()) -> kz_json:object(). +get_account_record_call_properties(AccountDoc) -> + %% Type: account/endpoint + kz_json:foldl(fun(Type, RecordingSettings, Acc) -> + kz_json:set_value(Type, merge_call_recording(RecordingSettings), Acc) end ,kz_json:new() - ,kz_json:get_json_value(<<"call_recording">>, JObj, kz_json:new()) + ,kzd_accounts:call_recording(AccountDoc, kz_json:new()) ). --spec get_endpoint_record_call_properties(kz_json:object()) -> kz_json:object(). -get_endpoint_record_call_properties(JObj) -> - CallRecording = kz_json:get_json_value(<<"call_recording">>, JObj), - case get_endpoint_record_call_properties(CallRecording, JObj) of - 'undefined' -> - kz_json:new(); - RecordCall -> - kz_json:from_list([{<<"endpoint">>, merge_call_recording(RecordCall)}]) +-spec get_user_record_call_properties(kzd_users:doc()) -> kz_json:object(). +get_user_record_call_properties(UserDoc) -> + case kzd_users:call_recording(UserDoc) of + 'undefined' -> get_legacy_record_call_properties(UserDoc); + CallRecording -> endpoint_recording(CallRecording) + end. + +-spec get_device_record_call_properties(kzd_devices:doc() | 'undefined') -> kz_json:object(). +get_device_record_call_properties('undefined') -> kz_json:new(); +get_device_record_call_properties(DeviceDoc) -> + case kzd_users:call_recording(DeviceDoc) of + 'undefined' -> get_legacy_record_call_properties(DeviceDoc); + CallRecording -> endpoint_recording(CallRecording) end. --spec get_endpoint_record_call_properties(kz_term:api_object(), kz_json:object()) -> kz_term:api_object(). -get_endpoint_record_call_properties('undefined', JObj) -> - Legacy = get_record_call_properties(JObj), +-spec get_legacy_record_call_properties(kz_json:object()) -> kz_json:object(). +get_legacy_record_call_properties(EndpointDoc) -> + Legacy = get_record_call_properties(EndpointDoc), case kz_json:is_true(<<"record_call">>, Legacy) of - 'false' -> 'undefined'; + 'false' -> kz_json:new(); 'true' -> + lager:info("adding legacy record_call settings"), Settings = kz_json:set_value(<<"enabled">>, 'true', Legacy), AnyNet = kz_json:from_list([{<<"onnet">>, Settings} ,{<<"offnet">>, Settings} ]), - kz_json:from_list([{<<"inbound">>, AnyNet} - ,{<<"outbound">>, AnyNet} - ]) - end; -get_endpoint_record_call_properties(JObj, _) -> - JObj. + RecordCall = kz_json:from_list([{<<"inbound">>, AnyNet} + ,{<<"outbound">>, AnyNet} + ]), + endpoint_recording(RecordCall) + end. + +-spec endpoint_recording(kzd_call_recording:doc()) -> kz_json:object(). +endpoint_recording(RecordCall) -> + kz_json:from_list([{<<"endpoint">>, merge_call_recording(RecordCall)}]). %% deprecated, to be removed -spec get_record_call_properties(kz_json:object()) -> kz_json:object(). @@ -888,9 +905,7 @@ maybe_start_metaflows(Call, Endpoints, _CallId) -> -spec maybe_start_metaflow(kapps_call:call(), kz_json:object()) -> 'ok'. maybe_start_metaflow(Call, Endpoint) -> - case not is_sms(Call) - andalso kz_json:get_first_defined([<<"metaflows">>, <<"Metaflows">>], Endpoint) - of + case kz_json:get_first_defined([<<"metaflows">>, <<"Metaflows">>], Endpoint) of 'false' -> 'ok'; 'undefined' -> 'ok'; ?EMPTY_JSON_OBJECT -> 'ok'; @@ -1989,8 +2004,7 @@ endpoint_actions(Endpoint, Call) -> -spec endpoint_actions(kz_json:object(), kapps_call:call(), kz_term:api_object()) -> kz_json:object(). endpoint_actions(Endpoint, Call, CallFwd) -> - Funs = [fun maybe_record_endpoint/1 - ], + Funs = [fun maybe_record_endpoint/1], Acc0 = {Endpoint, Call, CallFwd, kz_json:new()}, {_Endpoint, _Call, _CallFwd, Actions} = lists:foldr(fun(F, Acc) -> F(Acc) end, Acc0, Funs), Actions. @@ -1998,21 +2012,17 @@ endpoint_actions(Endpoint, Call, CallFwd) -> -type actions_acc() :: {kz_json:object(), kapps_call:call(), kz_term:api_object(), kz_json:object()}. -spec maybe_record_endpoint(actions_acc()) -> actions_acc(). -maybe_record_endpoint({Endpoint, Call, CallFwd, Actions} = Acc) -> - case is_sms(Call) - orelse kapps_call:call_id_direct(Call) =:= 'undefined' - of - 'true' -> Acc; - 'false' -> - Inception = kapps_call:inception_type(Call), - Data = kz_json:get_json_value(?ENDPOINT_INBOUND_RECORDING(Inception), Endpoint), - case Data /= 'undefined' - andalso kz_json:is_true(<<"enabled">>, Data) - of - 'false' -> Acc; - '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, Actions), - {Endpoint, Call, CallFwd, NewActions} - end +maybe_record_endpoint({_Endpoint, Call, _CallFwd, _Actions} = Acc) -> + ShouldNotCheck = is_sms(Call) + orelse kapps_call:call_id_direct(Call) =:= 'undefined', + maybe_record_endpoint(Acc, ShouldNotCheck). + +-spec maybe_record_endpoint(actions_acc(), boolean()) -> actions_acc(). +maybe_record_endpoint(Acc, 'true') -> Acc; +maybe_record_endpoint({Endpoint, Call, CallFwd, Actions}=Acc, 'false') -> + case kz_endpoint_recording:maybe_record_inbound(kapps_call:inception_type(Call), Endpoint, Call) of + 'false' -> Acc; + {'true', {ActionKey, ActionApp}} -> + NewActions = kz_json:set_value(ActionKey, ActionApp, Actions), + {Endpoint, Call, CallFwd, NewActions} end. diff --git a/core/kazoo_endpoint/src/kz_endpoint_recording.erl b/core/kazoo_endpoint/src/kz_endpoint_recording.erl index cc32d53668b..1663353ec4d 100644 --- a/core/kazoo_endpoint/src/kz_endpoint_recording.erl +++ b/core/kazoo_endpoint/src/kz_endpoint_recording.erl @@ -21,12 +21,20 @@ ,code_change/3 ]). +-export([maybe_record_inbound/3 + ,maybe_record_outbound/3 + ]). + -include("kazoo_endpoint.hrl"). -define(SERVER, ?MODULE). -define(MEDIA_RECORDING_ENDPOINT_ID, <<"Media-Recording-Endpoint-ID">>). +-define(ENDPOINT_INBOUND_RECORDING(Network), [<<"call_recording">>, <<"endpoint">>, <<"inbound">>, Network]). +-define(ENDPOINT_OUTBOUND_RECORDING(Network), [<<"call_recording">>, <<"endpoint">>, <<"outbound">>, Network]). +-define(ENDPOINT_OUTBOUND_RECORDING_LABEL(Network), <<"outbound to ", Network/binary, " from endpoint">>). + -type media_directory() :: file:filename_all(). -type media_name() :: file:filename_all(). -type media() :: {media_directory() | 'undefined', media_name()}. @@ -513,3 +521,41 @@ maybe_save_recording(_Pid, EndpointId, JObj) -> ,origin => <<"inbound from ", Inception/binary, " to endpoint">> }, save_recording(Store). + +%% @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', {kz_json:path(), kz_json:object()}} | 'false'. +maybe_record_inbound(FromNetwork, Endpoint, Call) -> + maybe_record_inbound(FromNetwork, Endpoint, Call, kz_json:get_json_value(?ENDPOINT_INBOUND_RECORDING(FromNetwork), Endpoint)). + +-spec maybe_record_inbound(kz_term:ne_binary(), kz_json:object(), kapps_call:call(), kz_term:api_object()) -> + {'true', {kz_json:path(), kz_json:object()}} | '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' -> + Values = [{<<"origin">>, <<"inbound from ", FromNetwork/binary, " to endpoint">>}], + App = record_call_command(kz_doc:id(Endpoint), FromNetwork, kz_json:set_values(Values, Data), Call), + lager:info("setting endpoint ~s to record on answer", [kz_doc:id(Endpoint)]), + {'true', {[<<"Execute-On-Answer">>, <<"Record-Endpoint">>], App}} + 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(?ENDPOINT_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">>, ?ENDPOINT_OUTBOUND_RECORDING_LABEL(ToNetwork), Data), + {'true' + ,kapps_call:start_recording(LabeledData, kapps_call:kvs_store('recording_follow_transfer', 'false', Call)) + } + end. diff --git a/core/kazoo_endpoint/test/kz_endpoint_tests.erl b/core/kazoo_endpoint/test/kz_endpoint_tests.erl index cbed013d567..80c2be1578f 100644 --- a/core/kazoo_endpoint/test/kz_endpoint_tests.erl +++ b/core/kazoo_endpoint/test/kz_endpoint_tests.erl @@ -11,3 +11,192 @@ attributes_keys_unique_test_() -> Keys = kz_endpoint:attributes_keys(), [?_assertEqual(length(Keys), length(lists:usort(Keys)))]. + +call_recording_test_() -> + [all_off() + ,all_on() + ,device_on() + ,device_off_user_on() + ,device_undefined_user_on() + ,device_off_user_off_account_on() + ,device_undefined_user_off_account_on() + ,device_undefined_user_undefined_account_on() + ]. + +device_on() -> + Expectations = [{<<"account">>, <<"inbound">>, <<"offnet">>, 'false'} + ,{<<"account">>, <<"inbound">>, <<"onnet">>, 'false'} + ,{<<"account">>, <<"outbound">>, <<"offnet">>, 'false'} + ,{<<"account">>, <<"outbound">>, <<"onnet">>, 'false'} + ,{<<"endpoint">>, <<"inbound">>, <<"offnet">>, 'true'} + ,{<<"endpoint">>, <<"inbound">>, <<"onnet">>, 'true'} + ,{<<"endpoint">>, <<"outbound">>, <<"offnet">>, 'true'} + ,{<<"endpoint">>, <<"outbound">>, <<"onnet">>, 'true'} + ], + + AccountDoc = kzd_accounts:set_call_recording(kzd_accounts:new(), kz_json:from_list([{<<"account">>, all_off_object()}])), + UserDoc = kzd_users:set_call_recording(kzd_users:new(), all_off_object()), + DeviceDoc = kzd_devices:set_call_recording(kzd_devices:new(), all_on_object()), + + Merged = kz_endpoint:merge_attribute(<<"call_recording">>, AccountDoc, DeviceDoc, UserDoc), + + check_expectations(?FUNCTION_NAME, Merged, Expectations). + +device_off_user_on() -> + Expectations = [{<<"account">>, <<"inbound">>, <<"offnet">>, 'false'} + ,{<<"account">>, <<"inbound">>, <<"onnet">>, 'false'} + ,{<<"account">>, <<"outbound">>, <<"offnet">>, 'false'} + ,{<<"account">>, <<"outbound">>, <<"onnet">>, 'false'} + ,{<<"endpoint">>, <<"inbound">>, <<"offnet">>, 'false'} + ,{<<"endpoint">>, <<"inbound">>, <<"onnet">>, 'false'} + ,{<<"endpoint">>, <<"outbound">>, <<"offnet">>, 'false'} + ,{<<"endpoint">>, <<"outbound">>, <<"onnet">>, 'false'} + ], + + AccountDoc = kzd_accounts:set_call_recording(kzd_accounts:new(), kz_json:from_list([{<<"account">>, all_off_object()}])), + UserDoc = kzd_users:set_call_recording(kzd_users:new(), all_on_object()), + DeviceDoc = kzd_devices:set_call_recording(kzd_devices:new(), all_off_object()), + + Merged = kz_endpoint:merge_attribute(<<"call_recording">>, AccountDoc, DeviceDoc, UserDoc), + + check_expectations(?FUNCTION_NAME, Merged, Expectations). + +device_undefined_user_on() -> + Expectations = [{<<"account">>, <<"inbound">>, <<"offnet">>, 'false'} + ,{<<"account">>, <<"inbound">>, <<"onnet">>, 'false'} + ,{<<"account">>, <<"outbound">>, <<"offnet">>, 'false'} + ,{<<"account">>, <<"outbound">>, <<"onnet">>, 'false'} + ,{<<"endpoint">>, <<"inbound">>, <<"offnet">>, 'true'} + ,{<<"endpoint">>, <<"inbound">>, <<"onnet">>, 'true'} + ,{<<"endpoint">>, <<"outbound">>, <<"offnet">>, 'true'} + ,{<<"endpoint">>, <<"outbound">>, <<"onnet">>, 'true'} + ], + + AccountDoc = kzd_accounts:set_call_recording(kzd_accounts:new(), kz_json:from_list([{<<"account">>, all_off_object()}])), + UserDoc = kzd_users:set_call_recording(kzd_users:new(), all_on_object()), + DeviceDoc = kzd_devices:new(), + + Merged = kz_endpoint:merge_attribute(<<"call_recording">>, AccountDoc, DeviceDoc, UserDoc), + + check_expectations(?FUNCTION_NAME, Merged, Expectations). + +device_off_user_off_account_on() -> + Expectations = [{<<"account">>, <<"inbound">>, <<"offnet">>, 'true'} + ,{<<"account">>, <<"inbound">>, <<"onnet">>, 'true'} + ,{<<"account">>, <<"outbound">>, <<"offnet">>, 'true'} + ,{<<"account">>, <<"outbound">>, <<"onnet">>, 'true'} + ,{<<"endpoint">>, <<"inbound">>, <<"offnet">>, 'false'} + ,{<<"endpoint">>, <<"inbound">>, <<"onnet">>, 'false'} + ,{<<"endpoint">>, <<"outbound">>, <<"offnet">>, 'false'} + ,{<<"endpoint">>, <<"outbound">>, <<"onnet">>, 'false'} + ], + + AccountDoc = kzd_accounts:set_call_recording(kzd_accounts:new(), kz_json:from_list([{<<"account">>, all_on_object()}])), + UserDoc = kzd_users:set_call_recording(kzd_users:new(), all_off_object()), + DeviceDoc = kzd_devices:set_call_recording(kzd_devices:new(), all_off_object()), + + Merged = kz_endpoint:merge_attribute(<<"call_recording">>, AccountDoc, DeviceDoc, UserDoc), + + check_expectations(?FUNCTION_NAME, Merged, Expectations). + +device_undefined_user_off_account_on() -> + Expectations = [{<<"account">>, <<"inbound">>, <<"offnet">>, 'true'} + ,{<<"account">>, <<"inbound">>, <<"onnet">>, 'true'} + ,{<<"account">>, <<"outbound">>, <<"offnet">>, 'true'} + ,{<<"account">>, <<"outbound">>, <<"onnet">>, 'true'} + ,{<<"endpoint">>, <<"inbound">>, <<"offnet">>, 'false'} + ,{<<"endpoint">>, <<"inbound">>, <<"onnet">>, 'false'} + ,{<<"endpoint">>, <<"outbound">>, <<"offnet">>, 'false'} + ,{<<"endpoint">>, <<"outbound">>, <<"onnet">>, 'false'} + ], + + AccountDoc = kzd_accounts:set_call_recording(kzd_accounts:new(), kz_json:from_list([{<<"account">>, all_on_object()}])), + UserDoc = kzd_users:set_call_recording(kzd_users:new(), all_off_object()), + DeviceDoc = kzd_devices:new(), + + Merged = kz_endpoint:merge_attribute(<<"call_recording">>, AccountDoc, DeviceDoc, UserDoc), + + check_expectations(?FUNCTION_NAME, Merged, Expectations). + +device_undefined_user_undefined_account_on() -> + Expectations = [{<<"account">>, <<"inbound">>, <<"offnet">>, 'true'} + ,{<<"account">>, <<"inbound">>, <<"onnet">>, 'true'} + ,{<<"account">>, <<"outbound">>, <<"offnet">>, 'true'} + ,{<<"account">>, <<"outbound">>, <<"onnet">>, 'true'} + ,{<<"endpoint">>, <<"inbound">>, <<"offnet">>, 'false'} + ,{<<"endpoint">>, <<"inbound">>, <<"onnet">>, 'false'} + ,{<<"endpoint">>, <<"outbound">>, <<"offnet">>, 'false'} + ,{<<"endpoint">>, <<"outbound">>, <<"onnet">>, 'false'} + ], + + AccountDoc = kzd_accounts:set_call_recording(kzd_accounts:new(), kz_json:from_list([{<<"account">>, all_on_object()}])), + UserDoc = kzd_users:new(), + DeviceDoc = kzd_devices:new(), + + Merged = kz_endpoint:merge_attribute(<<"call_recording">>, AccountDoc, DeviceDoc, UserDoc), + ?debugFmt("merged: ~p~n", [Merged]), + + check_expectations(?FUNCTION_NAME, Merged, Expectations). + +all_off() -> + Expectations = [{<<"account">>, <<"inbound">>, <<"offnet">>, 'false'} + ,{<<"account">>, <<"inbound">>, <<"onnet">>, 'false'} + ,{<<"account">>, <<"outbound">>, <<"offnet">>, 'false'} + ,{<<"account">>, <<"outbound">>, <<"onnet">>, 'false'} + ,{<<"endpoint">>, <<"inbound">>, <<"offnet">>, 'false'} + ,{<<"endpoint">>, <<"inbound">>, <<"onnet">>, 'false'} + ,{<<"endpoint">>, <<"outbound">>, <<"offnet">>, 'false'} + ,{<<"endpoint">>, <<"outbound">>, <<"onnet">>, 'false'} + ], + + AllOff = all_off_object(), + AccountDoc = kzd_accounts:set_call_recording(kzd_accounts:new(), kz_json:from_list([{<<"account">>, AllOff}])), + UserDoc = kzd_users:set_call_recording(kzd_users:new(), AllOff), + DeviceDoc = kzd_devices:set_call_recording(kzd_devices:new(), AllOff), + + Merged = kz_endpoint:merge_attribute(<<"call_recording">>, AccountDoc, DeviceDoc, UserDoc), + + check_expectations(?FUNCTION_NAME, Merged, Expectations). + +all_on() -> + Expectations = [{<<"account">>, <<"inbound">>, <<"offnet">>, 'true'} + ,{<<"account">>, <<"inbound">>, <<"onnet">>, 'true'} + ,{<<"account">>, <<"outbound">>, <<"offnet">>, 'true'} + ,{<<"account">>, <<"outbound">>, <<"onnet">>, 'true'} + ,{<<"endpoint">>, <<"inbound">>, <<"offnet">>, 'true'} + ,{<<"endpoint">>, <<"inbound">>, <<"onnet">>, 'true'} + ,{<<"endpoint">>, <<"outbound">>, <<"offnet">>, 'true'} + ,{<<"endpoint">>, <<"outbound">>, <<"onnet">>, 'true'} + ], + + AllOn = all_on_object(), + AccountDoc = kzd_accounts:set_call_recording(kzd_accounts:new(), kz_json:from_list([{<<"account">>, AllOn}])), + UserDoc = kzd_users:set_call_recording(kzd_users:new(), AllOn), + DeviceDoc = kzd_devices:set_call_recording(kzd_devices:new(), AllOn), + + Merged = kz_endpoint:merge_attribute(<<"call_recording">>, AccountDoc, DeviceDoc, UserDoc), + + check_expectations(?FUNCTION_NAME, Merged, Expectations). + +check_expectations(Test, Merged, Expectations) -> + [{kz_term:to_list(kz_binary:join([Test, Type, Direction, Network])) + ,?_assertEqual(Result, kz_json:is_true([<<"call_recording">>, Type, Direction, Network, <<"enabled">>], Merged)) + } || + {Type, Direction, Network, Result} <- Expectations + ]. + +all_off_object() -> + kz_json:set_values([{[Direction, Network, <<"enabled">>], 'false'} || + Direction <- [<<"inbound">>, <<"outbound">>], + Network <- [<<"onnet">>, <<"offnet">>] + ] + ,kz_json:new() + ). + +all_on_object() -> + kz_json:set_values([{[Direction, Network, <<"enabled">>], 'true'} || + Direction <- [<<"inbound">>, <<"outbound">>], + Network <- [<<"onnet">>, <<"offnet">>] + ] + ,kz_json:new() + ). diff --git a/doc/user_guides/call_recording.md b/doc/user_guides/call_recording.md index c1fc1924193..344b6e27146 100644 --- a/doc/user_guides/call_recording.md +++ b/doc/user_guides/call_recording.md @@ -23,6 +23,70 @@ Configuring recording at the user level starts recording for any calls to/from a Configuring recording at the device level starts recording for any calls to/from the device. +#### Precedence of settings + +Precedence of settings is: Device > User + +If a user turns on call recording but a device has explicitly disabled it, the device will not be recorded when the user makes a call with it. If the device's settings are left undefined, the user's settings will be applied. + +The account's settings are considered independently of the endpoint's. So a user who has disabled recording, within an account that has enabled recording, will still have calls recorded according to the account's settings. + +#### Recording settings matrix + +##### Account Settings + +When an onnet device makes an internal call: + +| Setting | Source | Destination | Recording Started | +| Account -> Inbound -> Onnet | onnet | onnet | yes | +| Account -> Inbound -> Offnet | onnet | onnet | no | +| Account -> Outbound -> Onnet | onnet | onnet | yes (if inbound -> onnet isn't configured) | +| Account -> Outbound -> Offnet | onnet | onnet | no | + +When an onnet device makes an external call: + +| Setting | Source | Destination | Recording Started | +| Account -> Inbound -> Onnet | onnet | offnet | yes | +| Account -> Inbound -> Offnet | onnet | offnet | no | +| Account -> Outbound -> Onnet | onnet | offnet | no | +| Account -> Outbound -> Offnet | onnet | offnet | yes (if inbound -> onnet isn't configured) | + +When an offnet device makes an internal call: + +| Setting | Source | Destination | Recording Started | +| Account -> Inbound -> Onnet | offnet | onnet | no | +| Account -> Inbound -> Offnet | offnet | onnet | yes | +| Account -> Outbound -> Onnet | offnet | onnet | yes (if inbound-offnet isn't configured) | +| Account -> Outbound -> Offnet | offnet | onnet | no | + +##### Endpoint Settings + +When an onnet device makes an internal call: + +| Setting | Source | Destination | Recording Started | +| Endpoint -> Inbound -> Onnet | onnet | onnet | yes¹ | +| Endpoint -> Inbound -> Offnet | onnet | onnet | no | +| Endpoint -> Outbound -> Onnet | onnet | onnet | yes | +| Endpoint -> Outbound -> Offnet | onnet | onnet | no | + +When an onnet device makes an external call: + +| Setting | Source | Destination | Recording Started | +| Endpoint -> Inbound -> Onnet | onnet | offnet | no | +| Endpoint -> Inbound -> Offnet | onnet | offnet | no | +| Endpoint -> Outbound -> Onnet | onnet | offnet | no | +| Endpoint -> Outbound -> Offnet | onnet | offnet | yes | + +When an offnet endpoint makes a call to an onnet device: + +| Setting | Source | Destination | Recording Started | +| Endpoint -> Inbound -> Onnet | offnet | onnet | no | +| Endpoint -> Inbound -> Offnet | offnet | onnet | yes | +| Endpoint -> Outbound -> Onnet | offnet | onnet | no | +| Endpoint -> Outbound -> Offnet | offnet | onnet | no | + +#### Enabling recording + To enable call recording, add `"call_recording":{...}` to the document of choice. For example, if you have a user with user ID of `{USER_ID}`, you can patch the user's document using Crossbar: ```shell diff --git a/scripts/no_raw_json.escript b/scripts/no_raw_json.escript index a4594586916..3463e23a680 100755 --- a/scripts/no_raw_json.escript +++ b/scripts/no_raw_json.escript @@ -6,18 +6,29 @@ -export([main/1]). -main(_) -> - case raw_json_usage:process_project() of - [] -> 'ok'; - ModulesWithRawJSON -> - handle_potential_usage(ModulesWithRawJSON) - end. +main([]) -> + io:format("checking raw JSON {[]} usage: "), + ModulesWithRawJSON = raw_json_usage:process_project(), + handle_potential_usage(ModulesWithRawJSON); +main(Files) -> + io:format("checking raw JSON {[]} usage: "), + ModulesWithRawJSON = lists:foldl(fun process_file/2, [], Files), + handle_potential_usage(ModulesWithRawJSON). + +process_file(File, Acc) -> + process_file(File, Acc, filename:extension(kz_term:to_binary(File))). + +process_file(File, Acc, <<".erl">>) -> + ModuleName = filename:basename(File, ".erl"), + Acc ++ raw_json_usage:process_module(kz_term:to_atom(ModuleName, 'true')); +process_file(_File, Acc, _Ext) -> Acc. handle_potential_usage(ModulesWithRawJSON) -> ExitCode = lists:foldl(fun handle_potential_usage/2 ,0 ,ModulesWithRawJSON ), + io:format("~n"), erlang:halt(ExitCode). handle_potential_usage({Module, Lines}, ExitCode) -> @@ -96,7 +107,7 @@ find_source(Module, BeamFile) when is_list(BeamFile) -> case file:open(SrcFile, ['read', 'binary', 'raw', 'read_ahead']) of {'ok', IODevice} -> IODevice; {'error', 'enoent'} -> - io:format("failed to find source ~s for beam ~s~n", [SrcFile, BeamFile]), + io:format("failed to find module ~s source file ~s in ~s for beam ~s~n", [Module, SrcFile, AppDir, BeamFile]), throw({'error', 'enoent'}) end. @@ -115,7 +126,7 @@ find_source_sub_dir(Module, AppDir) -> end. output_raw_matches(Module, Line, 0, LineData, Matches) -> - File = props:get_value(source, Module:module_info(compile), Module), + File = props:get_value('source', Module:module_info('compile'), Module), Format = "~s:~p: ~s~n", output(File, Line, Matches, LineData, Format); output_raw_matches(Module, Line, _RawLines, LineData, Matches) ->