From 46b8424ccb6c50163130295f816d6922cc6f40aa Mon Sep 17 00:00:00 2001 From: James Aimonetti Date: Fri, 1 May 2020 17:04:41 -0700 Subject: [PATCH] KCRO-24: address pagination issue when querying MODBs (#6520) Prior, when page_size and filters are in play with a query, the next_db clause would not match (since PageSize wasn't 'infinity'). This would lead to results being missed in the intial query and subsequent queries would skip over to the next group of results. Instead, consider if there is a next last key only for whether to query the current DB again. --- applications/crossbar/src/crossbar_doc.erl | 26 +- applications/crossbar/src/crossbar_view.erl | 4 +- .../crossbar/src/modules/cb_vmboxes.erl | 34 ++- core/kazoo_documents/src/kzd_box_message.erl | 16 +- core/kazoo_proper/src/pqc_cb_api.erl | 4 +- core/kazoo_proper/src/pqc_cb_vmboxes.erl | 222 +++++++++++++++++- core/kazoo_web/src/kz_http.erl | 18 +- 7 files changed, 280 insertions(+), 44 deletions(-) diff --git a/applications/crossbar/src/crossbar_doc.erl b/applications/crossbar/src/crossbar_doc.erl index 8c7b6e83d47..c998a8630fe 100644 --- a/applications/crossbar/src/crossbar_doc.erl +++ b/applications/crossbar/src/crossbar_doc.erl @@ -302,8 +302,8 @@ load_docs(Context, Filter) {'error', Error} -> handle_datamgr_errors(Error, <<"all_docs">>, Context); {'ok', JObjs} -> Filtered = [JObj - || JObj <- lists:foldl(Fun, [], JObjs) - ,(not kz_term:is_empty(JObj)) + || JObj <- lists:foldl(Fun, [], JObjs), + (not kz_term:is_empty(JObj)) ], handle_datamgr_success(Filtered, Context) end. @@ -319,7 +319,7 @@ load_docs(Context, Filter) cb_context:context(). load_attachment({DocType, DocId}, AName, Options, Context) -> load_attachment(DocId, AName, [{'doc_type', DocType} | Options], Context); -load_attachment(<<_/binary>>=DocId, AName, Options, Context) -> +load_attachment(<>, AName, Options, Context) -> case kz_datamgr:fetch_attachment(cb_context:db_name(Context), DocId, AName, Options) of {'error', Error} -> handle_datamgr_errors(Error, DocId, Context); {'ok', AttachBin} -> @@ -516,17 +516,25 @@ handle_saved_attachment(Context, DocId) -> load(DocId, Context). -spec maybe_delete_doc(cb_context:context(), kz_term:ne_binary()) -> - {'ok', _} | - {'error', any()}. + {'ok', 'non_empty'} | + {'ok', kz_json:object()} | + kz_datamgr:data_error(). maybe_delete_doc(Context, DocId) -> AccountDb = cb_context:db_name(Context), case kz_datamgr:open_doc(AccountDb, DocId) of {'error', _}=Error -> Error; {'ok', JObj} -> - case kz_doc:attachments(JObj) of - 'undefined' -> kz_datamgr:del_doc(AccountDb, JObj); - _Attachments -> {'ok', 'non_empty'} - end + delete_if_empty_attachments(AccountDb, JObj) + end. + +-spec delete_if_empty_attachments(kz_term:ne_binary(), kz_json:object()) -> + {'ok', 'non_empty'} | + {'ok', kz_json:object()} | + kz_datamgr:data_error(). +delete_if_empty_attachments(AccountDb, JObj) -> + case kz_doc:attachments(JObj) of + 'undefined' -> kz_datamgr:del_doc(AccountDb, JObj); + _Attachments -> {'ok', 'non_empty'} end. %% @equiv delete(Context, cb_context:should_soft_delete(Context)) diff --git a/applications/crossbar/src/crossbar_view.erl b/applications/crossbar/src/crossbar_view.erl index 607350ffaee..9d4d4fce10c 100644 --- a/applications/crossbar/src/crossbar_view.erl +++ b/applications/crossbar/src/crossbar_view.erl @@ -915,7 +915,7 @@ handle_query_result(#{is_chunked := 'true' ,context => Context1 ,previous_chunk_length => FilteredLength }; -handle_query_result(#{page_size := PageSize +handle_query_result(#{page_size := _PageSize ,last_key := _OldLastKey }=LoadMap ,[_|RestDbs] @@ -925,7 +925,7 @@ handle_query_result(#{page_size := PageSize ) -> case check_page_size_and_length(LoadMap, FilteredLength, FilteredJObjs, NewLastKey) of {'exhausted', LoadMap2} -> LoadMap2; - {'next_db', LoadMap2} when PageSize =:= 'infinity', NewLastKey =/= 'undefined' -> + {'next_db', LoadMap2} when NewLastKey =/= 'undefined' -> lager:debug("updating new last key to ~p from ~p", [NewLastKey, _OldLastKey]), get_results(LoadMap2#{last_key => NewLastKey}); {'next_db', LoadMap2} -> get_results(LoadMap2#{databases => RestDbs}) diff --git a/applications/crossbar/src/modules/cb_vmboxes.erl b/applications/crossbar/src/modules/cb_vmboxes.erl index 9e85b695c0d..d50ab4769fa 100644 --- a/applications/crossbar/src/modules/cb_vmboxes.erl +++ b/applications/crossbar/src/modules/cb_vmboxes.erl @@ -406,7 +406,8 @@ put(Context) -> -spec put(cb_context:context(), path_token(), path_token()) -> cb_context:context(). put(Context, _BoxId, ?MESSAGES_RESOURCE) -> - maybe_save_attachment(crossbar_doc:save(Context)). + Context1 = crossbar_doc:save(Context), + maybe_save_attachment(Context1). -spec put(cb_context:context(), path_token(), path_token(), path_token(), path_token()) -> cb_context:context(). put(Context, _BoxId, ?MESSAGES_RESOURCE, MsgID, ?BIN_DATA) -> @@ -524,27 +525,36 @@ check_mailbox_for_messages_array(Context, VMBoxMsgs) -> -spec maybe_save_attachment(cb_context:context()) -> cb_context:context(). maybe_save_attachment(Context) -> - maybe_save_attachment(crossbar_util:maybe_remove_attachments(Context), cb_context:req_files(Context)). + maybe_save_attachment(Context, cb_context:resp_status(Context)). --spec maybe_save_attachment(cb_context:context(), req_files()) -> cb_context:context(). -maybe_save_attachment(Context, []) -> Context; -maybe_save_attachment(Context, [{Filename, FileJObj} | _Others]) -> +maybe_save_attachment(Context, 'success') -> + save_attachment(crossbar_util:maybe_remove_attachments(Context), cb_context:req_files(Context)); +maybe_save_attachment(Context, _Error) -> + lager:info("saving message metadata failed"), + Context. + +-spec save_attachment(cb_context:context(), req_files()) -> cb_context:context(). +save_attachment(Context, []) -> Context; +save_attachment(Context, [{Filename, FileJObj} | _Others]) -> save_attachment(Context, Filename, FileJObj). -spec save_attachment(cb_context:context(), binary(), kz_json:object()) -> cb_context:context(). save_attachment(Context, Filename, FileJObj) -> JObj = cb_context:doc(Context), - BoxId = kz_doc:id(JObj), + MessageId = kz_doc:id(JObj), + Contents = kz_json:get_ne_binary_value(<<"contents">>, FileJObj), + CT = kz_json:get_ne_binary_value([<<"headers">>, <<"content_type">>], FileJObj), Opts = [{'content_type', CT} ,{'rev', kz_doc:revision(JObj)} | ?TYPE_CHECK_OPTION(<<"mailbox_message">>) ], AttName = cb_modules_util:attachment_name(Filename, CT), - C1 = crossbar_doc:save_attachment(BoxId, AttName, Contents, Context, Opts), + C1 = crossbar_doc:save_attachment(MessageId, AttName, Contents, Context, Opts), case cb_context:resp_status(C1) of 'success' -> + lager:info("saved attachment to message ~s", [MessageId]), C2 = crossbar_util:response(kzd_box_message:metadata(JObj), C1), update_mwi(C2, kzd_box_message:source_id(JObj)); _ -> C1 @@ -993,6 +1003,7 @@ create_new_message_document(Context, BoxJObj) -> From = kz_json:get_ne_binary_value(<<"from">>, Doc, CallerNumber), To = kz_json:get_ne_binary_value(<<"to">>, Doc, BoxNum), Length = kz_json:get_integer_value(<<"length">>, Doc, 1), + Folder = kz_json:get_ne_binary_value(<<"folder">>, Doc, <<"new">>), Timestamp = kz_doc:created(JObj), @@ -1003,7 +1014,7 @@ create_new_message_document(Context, BoxJObj) -> ,{fun kapps_call:set_caller_id_name/2, CallerName} ], Call = kapps_call:exec(Routines, kapps_call:new()), - Metadata = kzd_box_message:build_metadata_object(Length, Call, MsgId, CallerNumber, CallerName, Timestamp), + Metadata = kzd_box_message:build_metadata_object(Length, Call, MsgId, CallerNumber, CallerName, Timestamp, Folder), Message = kzd_box_message:update_media_id(MsgId, kzd_box_message:set_metadata(Metadata, JObj)), cb_context:set_doc(cb_context:set_db_name(Context, kz_doc:account_db(Message)), Message). @@ -1166,10 +1177,7 @@ del_dir(Dir) -> -spec del_all_files(string()) -> any(). del_all_files(Dir) -> {'ok', Files} = file:list_dir(Dir), - lists:foreach(fun(F) -> - file:delete(Dir ++ F) - end, Files - ). + lists:foreach(fun(F) -> file:delete(Dir ++ F) end, Files). %%------------------------------------------------------------------------------ %% @doc generate a media name based on CallerID and creation date @@ -1206,7 +1214,7 @@ check_uniqueness(VMBoxId, Context) -> check_uniqueness(VMBoxId, Context, Mailbox) catch _:_ -> - lager:debug("can't convert mailbox number to integer", []), + lager:debug("can't convert mailbox number to integer"), 'false' end. diff --git a/core/kazoo_documents/src/kzd_box_message.erl b/core/kazoo_documents/src/kzd_box_message.erl index 532f4f6c505..d85cd50e16c 100644 --- a/core/kazoo_documents/src/kzd_box_message.erl +++ b/core/kazoo_documents/src/kzd_box_message.erl @@ -10,7 +10,7 @@ %%%----------------------------------------------------------------------------- -module(kzd_box_message). --export([new/2, build_metadata_object/6 +-export([new/2, build_metadata_object/6, build_metadata_object/7 ,count_folder/2 ,create_message_name/3 ,type/0 @@ -164,9 +164,10 @@ create_message_name(BoxNum, <>, UtcSeconds) -> -spec message_name(kz_term:ne_binary(), kz_time:datetime(), string()) -> kz_term:ne_binary(). message_name(BoxNum, {{Y,M,D},{H,I,S}}, TZ) -> list_to_binary(["mailbox ", BoxNum, " message " - ,kz_term:to_binary(M), "-" - ,kz_term:to_binary(D), "-" - ,kz_term:to_binary(Y), " " + ,kz_term:to_binary(Y), "-" + ,kz_date:pad_month(M), "-" + ,kz_date:pad_day(D), " " + ,kz_term:to_binary(H), ":" ,kz_term:to_binary(I), ":" ,kz_term:to_binary(S), TZ @@ -179,6 +180,11 @@ message_name(BoxNum, {{Y,M,D},{H,I,S}}, TZ) -> -spec build_metadata_object(pos_integer(), kapps_call:call(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_time:gregorian_seconds()) -> doc(). build_metadata_object(Length, Call, MediaId, CIDNumber, CIDName, Timestamp) -> + build_metadata_object(Length, Call, MediaId, CIDNumber, CIDName, Timestamp, ?VM_FOLDER_NEW). + +-spec build_metadata_object(pos_integer(), kapps_call:call(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_time:gregorian_seconds(), kz_term:ne_binary()) -> + doc(). +build_metadata_object(Length, Call, MediaId, CIDNumber, CIDName, Timestamp, Folder) -> kz_json:from_list( [{?KEY_MEDIA_ID, MediaId} ,{?KEY_META_CALL_ID, kapps_call:call_id(Call)} @@ -197,7 +203,7 @@ build_metadata_object(Length, Call, MediaId, CIDNumber, CIDName, Timestamp) -> ,{?KEY_META_TO_USER, kapps_call:to_user(Call)} ,{?KEY_META_TO_REALM, kapps_call:to_realm(Call)} - ,{?VM_KEY_FOLDER, ?VM_FOLDER_NEW} + ,{?VM_KEY_FOLDER, Folder} ]). -spec get_msg_id(kz_json:object()) -> kz_term:api_ne_binary(). diff --git a/core/kazoo_proper/src/pqc_cb_api.erl b/core/kazoo_proper/src/pqc_cb_api.erl index c469d4614b3..18c5af896b0 100644 --- a/core/kazoo_proper/src/pqc_cb_api.erl +++ b/core/kazoo_proper/src/pqc_cb_api.erl @@ -189,13 +189,13 @@ default_request_headers(RequestId) -> | default_request_headers() ]. --spec make_request(expectations(), fun_2(), string(), request_headers()) -> +-spec make_request(expectations(), fun_2(), iolist(), request_headers()) -> response(). make_request(Expectations, HTTP, URL, RequestHeaders) -> ?INFO("~p(~s, ~s)", [HTTP, URL, RequestHeaders]), handle_response(Expectations, HTTP(URL, RequestHeaders)). --spec make_request(expectations(), fun_3(), string(), request_headers(), iodata()) -> +-spec make_request(expectations(), fun_3(), iolist(), request_headers(), iodata()) -> response(). make_request(Expectations, HTTP, URL, RequestHeaders, RequestBody) -> ?INFO("~p: ~s", [HTTP, URL]), diff --git a/core/kazoo_proper/src/pqc_cb_vmboxes.erl b/core/kazoo_proper/src/pqc_cb_vmboxes.erl index 0afdd0927f3..246b41fd463 100644 --- a/core/kazoo_proper/src/pqc_cb_vmboxes.erl +++ b/core/kazoo_proper/src/pqc_cb_vmboxes.erl @@ -12,15 +12,17 @@ -export([new_message/5 ,fetch_message_metadata/4 ,fetch_message_binary/4 + ,list_messages/3, list_messages/4 ,create_box/3 ,delete_box/3 ]). --export([seq/0 +-export([seq/0, seq_kcro_24/0 ,cleanup/0 ]). -include("kazoo_proper.hrl"). +-include_lib("kazoo_stdlib/include/kz_types.hrl"). -define(ACCOUNT_NAMES, [<>]). @@ -84,6 +86,21 @@ fetch_message_binary(API, AccountId, BoxId, MessageId) -> ,RequestHeaders ). +-spec list_messages(pqc_cb_api:state(), kz_term:ne_binary(), kz_term:ne_binary()) -> pqc_cb_api:response(). +list_messages(API, AccountId, BoxId) -> + list_messages(API, AccountId, BoxId, []). + +-spec list_messages(pqc_cb_api:state(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:proplist()) -> pqc_cb_api:response(). +list_messages(API, AccountId, BoxId, QuerystringParams) -> + MessagesURL = messages_url(AccountId, BoxId, QuerystringParams), + + Expectations = [#expectation{response_codes = [200]}], + pqc_cb_api:make_request(Expectations + ,fun kz_http:get/2 + ,MessagesURL + ,pqc_cb_api:request_headers(API) + ). + -spec create_box(pqc_cb_api:state(), kz_term:ne_binary(), kz_term:ne_binary()) -> pqc_cb_api:response(). create_box(API, AccountId, BoxName) -> BoxesURL = boxes_url(AccountId), @@ -120,7 +137,12 @@ box_url(AccountId, BoxId) -> string:join([pqc_cb_accounts:account_url(AccountId), "vmboxes", kz_term:to_list(BoxId)], "/"). messages_url(AccountId, BoxId) -> - string:join([pqc_cb_accounts:account_url(AccountId), "vmboxes", kz_term:to_list(BoxId), "messages"], "/"). + messages_url(AccountId, BoxId, []). + +messages_url(AccountId, BoxId, QuerystringParams) -> + [string:join([pqc_cb_accounts:account_url(AccountId), "vmboxes", kz_term:to_list(BoxId), "messages"], "/") + ,"?", kz_http_util:props_to_querystring(QuerystringParams) + ]. message_url(AccountId, BoxId, MessageId) -> string:join([pqc_cb_accounts:account_url(AccountId), "vmboxes", kz_term:to_list(BoxId), "messages", kz_term:to_list(MessageId)], "/"). @@ -130,11 +152,164 @@ message_bin_url(AccountId, BoxId, MessageId) -> -spec seq() -> 'ok'. seq() -> - Fs = [fun seq_kzoo_52/0], + Fs = [fun seq_kzoo_52/0 + ,fun seq_kcro_24/0 + ], lists:foreach(fun run/1, Fs). run(F) -> F(). +-spec seq_kcro_24() -> 'ok'. +seq_kcro_24() -> + Model = initial_state(), + API = pqc_kazoo_model:api(Model), + + AccountResp = pqc_cb_accounts:create_account(API, hd(?ACCOUNT_NAMES)), + lager:info("created account: ~s", [AccountResp]), + + AccountId = kz_json:get_value([<<"data">>, <<"id">>], kz_json:decode(AccountResp)), + + BoxName = kz_binary:rand_hex(4), + CreateResp = create_box(API, AccountId, BoxName), + lager:info("created box resp: ~s", [CreateResp]), + + CreatedBox = kz_json:get_json_value(<<"data">>, kz_json:decode(CreateResp)), + BoxId = kz_doc:id(CreatedBox), + + %% create messages + {{Year, Month, Day}, _} = calendar:universal_time(), + CurrentMonth = {{Year, Month, Day} + ,CurrentMsgs = [{<<"0-new">>, <<"new">>} %% oldest + ,{<<"1-saved">>, <<"saved">>} + ,{<<"2-new">>, <<"new">>} + ,{<<"3-saved">>, <<"saved">>} + ,{<<"4-saved">>, <<"saved">>} + ,{<<"5-new">>, <<"new">>} + ,{<<"6-saved">>, <<"saved">>} + ,{<<"7-new">>, <<"new">>} + ,{<<"8-saved">>, <<"saved">>} %% newest + ] + }, + create_messages(API, AccountId, BoxId, CurrentMonth), + + {PrevYear, PrevMonth, PrevDay} = kz_date:normalize({Year, Month-1, Day}), + + LastMonth = {{PrevYear, PrevMonth, PrevDay} + ,OlderMsgs = [{<<"10-new">>, <<"new">>} + ,{<<"11-saved">>, <<"saved">>} + ,{<<"12-new">>, <<"new">>} + ,{<<"13-saved">>, <<"saved">>} + ] + }, + create_messages(API, AccountId, BoxId, LastMonth), + + ExpectedCDRs = lists:reverse(CurrentMsgs) ++ lists:reverse(OlderMsgs), + ExpectedCDRIds = [CDRId || {CDRId, _} <- ExpectedCDRs], + NewCDRIds = [CDRId || {CDRId, <<"new">>} <- ExpectedCDRs], + + lager:info("expected CDR IDs: ~p", [ExpectedCDRIds]), + + %% The main principle to reproduce the issue seems to be to "tune" + %% page_size in such a way that it would be less than db size but + %% there are requested items in the rest of the db that satisfy + %% the filter. These items will be missed in the response. + + %% For example, the current MODB contains 4 new(n) items and 5 + %% saved(s) messages - "n s n s s n s n s", the order does matter. + + %% If we request new messages with "page_size=5" and filter for + %% non-saved messages, we will get only 2 "new" messages from this db, + %% as the code takes the next db because the page_size is reached, + %% independant on the fact that next_start_key is available and + %% the filtered out result less that page_size. + %% ------------------------------------------------------------------------ + ListedResp = list_messages(API, AccountId, BoxId + ,[{<<"page_size">>, 5} + ,{<<"filter_not_folder">>, <<"saved">>} + ]), + lager:info("listed messages ~s", [ListedResp]), + ListedJObj = kz_json:decode(ListedResp), + <<"success">> = kz_json:get_ne_binary_value(<<"status">>, ListedJObj), + 5 = kz_json:get_integer_value(<<"page_size">>, ListedJObj), + + lager:info("expecting ~p", [NewCDRIds]), + Listing = kz_json:get_list_value(<<"data">>, ListedJObj), + UpdatedCDRIds = remove_expected_messages(NewCDRIds, Listing), + + NextStartKey = kz_json:get_ne_binary_value(<<"next_start_key">>, ListedJObj), + + SecondPageResp = list_messages(API, AccountId, BoxId + ,[{<<"page_size">>, 5} + ,{<<"filter_not_folder">>, <<"saved">>} + ,{<<"start_key">>, NextStartKey} + ] + ), + lager:info("second page: ~s", [SecondPageResp]), + SecondPage = kz_json:decode(SecondPageResp), + <<"success">> = kz_json:get_ne_binary_value(<<"status">>, SecondPage), + 1 = kz_json:get_integer_value(<<"page_size">>, SecondPage), + + lager:info("expecting second page ~p", [UpdatedCDRIds]), + SecondListing = kz_json:get_list_value(<<"data">>, SecondPage), + [] = remove_expected_messages(UpdatedCDRIds, SecondListing), + + AllNewOnePageResp = list_messages(API, AccountId, BoxId + ,[{<<"filter_not_folder">>, <<"saved">>}] + ), + lager:info("all new messages ~s", [AllNewOnePageResp]), + AllNewJObj = kz_json:decode(AllNewOnePageResp), + <<"success">> = kz_json:get_ne_binary_value(<<"status">>, AllNewJObj), + 'true' = length(NewCDRIds) =:= kz_json:get_integer_value(<<"page_size">>, AllNewJObj), + AllNewListing = kz_json:get_list_value(<<"data">>, AllNewJObj), + [] = remove_expected_messages(NewCDRIds, AllNewListing), + + AllMessagesResp = list_messages(API, AccountId, BoxId), + lager:info("all messages ~s", [AllMessagesResp]), + AllJObj = kz_json:decode(AllMessagesResp), + <<"success">> = kz_json:get_ne_binary_value(<<"status">>, AllJObj), + + 'true' = length(ExpectedCDRIds) =:= kz_json:get_integer_value(<<"page_size">>, AllJObj), + AllListing = kz_json:get_list_value(<<"data">>, AllJObj), + [] = remove_expected_messages(ExpectedCDRIds, AllListing), + + %% no pagination enabled + NoPagingResp = list_messages(API, AccountId, BoxId, [{<<"paginate">>, 'false'}]), + lager:info("no paging messages ~s", [NoPagingResp]), + NoPagingJObj = kz_json:decode(NoPagingResp), + <<"success">> = kz_json:get_ne_binary_value(<<"status">>, NoPagingJObj), + + NoPagingListing = kz_json:get_list_value(<<"data">>, NoPagingJObj), + 'true' = length(ExpectedCDRIds) =:= length(NoPagingListing), + [] = remove_expected_messages(ExpectedCDRIds, NoPagingListing), + + %% no pagination enabled but with filter + NoPagingFilterResp = list_messages(API, AccountId, BoxId, [{<<"paginate">>, 'false'} + ,{<<"filter_not_folder">>, <<"saved">>} + ]), + lager:info("no paging but filtered messages ~s", [NoPagingFilterResp]), + NoPagingFilterJObj = kz_json:decode(NoPagingFilterResp), + <<"success">> = kz_json:get_ne_binary_value(<<"status">>, NoPagingFilterJObj), + + NoPagingFilterListing = kz_json:get_list_value(<<"data">>, NoPagingFilterJObj), + 'true' = length(NewCDRIds) =:= length(NoPagingFilterListing), + [] = remove_expected_messages(NewCDRIds, NoPagingFilterListing), + + + DeleteResp = delete_box(API, AccountId, BoxId), + lager:info("delete resp: ~s", [DeleteResp]), + DeletedBox = kz_json:get_json_value(<<"data">>, kz_json:decode(DeleteResp)), + BoxId = kz_doc:id(DeletedBox), + BoxName = kzd_vmboxes:name(DeletedBox), + 'true' = kz_json:is_true([<<"_read_only">>, <<"deleted">>], DeletedBox), + + cleanup(API), + lager:info("FINISHED KCRO-24"). + +remove_expected_messages(CDRIds, []) -> CDRIds; +remove_expected_messages([CDRId | CDRIds], [Message | Messages]) -> + CDRId = kz_json:get_ne_binary_value(<<"call_id">>, Message), + remove_expected_messages(CDRIds, Messages). + seq_kzoo_52() -> Model = initial_state(), API = pqc_kazoo_model:api(Model), @@ -146,7 +321,7 @@ seq_kzoo_52() -> BoxName = kz_binary:rand_hex(4), CreateResp = create_box(API, AccountId, BoxName), - lager:info("create resp: ~s", [CreateResp]), + lager:info("created box resp: ~s", [CreateResp]), CreatedBox = kz_json:get_json_value(<<"data">>, kz_json:decode(CreateResp)), BoxId = kz_doc:id(CreatedBox), BoxName = kzd_vmboxes:name(CreatedBox), @@ -159,7 +334,7 @@ seq_kzoo_52() -> 'true' = kz_json:is_true([<<"_read_only">>, <<"deleted">>], DeletedBox), cleanup(API), - lager:info("FINISHED SEQ"). + lager:info("FINISHED KZOO-52"). -spec initial_state() -> pqc_kazoo_model:model(). initial_state() -> @@ -192,3 +367,40 @@ cleanup(API) -> cleanup_system(). cleanup_system() -> 'ok'. + +create_messages(API, AccountId, BoxId, {{Year, Month, Day}, Folders}) -> + MODB = kzs_util:format_account_id(AccountId, Year, Month), + 'true' = kazoo_modb:create(MODB), + + {'ok', MP3} = file:read_file(filename:join([code:priv_dir('kazoo_proper'), "mp3.mp3"])), + + StartTime = calendar:datetime_to_gregorian_seconds({{Year, Month, Day}, {0, 0, 0}}), + + {_StartTime, Messages} = lists:foldl(fun create_voicemail/2 + ,{StartTime, []} + ,Folders + ), + lists:foreach(fun(M) -> + <<_/binary>> = new_message(API, AccountId, BoxId, M, MP3) + end + ,Messages + ). + +create_voicemail(Folder, {Timestamp, Messages}) -> + Message = create_message(Folder, Timestamp), + {Timestamp + ?SECONDS_IN_HOUR, [Message | Messages]}. + +create_message({CallId, Folder}, Timestamp) -> + kz_json:from_list([{<<"call_id">>, CallId} + ,{<<"timestamp">>, Timestamp} + ,{<<"folder">>, Folder} + ,{<<"metadata">> + ,kz_json:from_list([{<<"call_id">>, CallId} + ,{<<"caller_id_name">>, <>} + ,{<<"caller_id_number">>, <>} + ,{<<"length">>, 0} + ,{<<"folder">>, Folder} + ,{<<"timestamp">>, Timestamp} + ]) + } + ]). diff --git a/core/kazoo_web/src/kz_http.erl b/core/kazoo_web/src/kz_http.erl index 8597aae308c..3a0416c7d1b 100644 --- a/core/kazoo_web/src/kz_http.erl +++ b/core/kazoo_web/src/kz_http.erl @@ -286,22 +286,22 @@ handle_response({'ok', {{_, StatusCode, _}, Headers, Body}}) when is_integer(StatusCode) -> {'ok', StatusCode, Headers, Body}; handle_response({'error', 'timeout'}=Err) -> - lager:debug("connection timeout"), + lager:info("connection timeout"), Err; handle_response({'EXIT', {Error,_Trace}}) -> - lager:debug("caught EXIT ~p: ~p", [Error, _Trace]), + lager:info("caught EXIT ~p: ~p", [Error, _Trace]), {'error', Error}; handle_response({'error', {'failed_connect',[{_, _Address}, {_, _, 'nxdomain'}]}}) -> - lager:debug("non existent domain ~p", [_Address]), + lager:info("non existent domain ~p", [_Address]), {'error', {'failed_connect', 'nxdomain'}}; handle_response({'error', {'failed_connect',[{_, _Address}, {_, _, 'econnrefused'}]}}) -> - lager:debug("connection refused to ~p", [_Address]), + lager:info("connection refused to ~p", [_Address]), {'error', {'failed_connect', 'econnrefused'}}; handle_response({'error', {'malformed_url', _, Url}}) -> - lager:debug("failed to parse URL ~p", [Url]), + lager:info("failed to parse URL ~p", [Url]), {'error', {'malformed_url', Url}}; handle_response({'error', Error}=Err) -> - lager:debug("request failed with ~p", [Error]), + lager:info("request failed with ~p", [Error]), Err. %%------------------------------------------------------------------------------ @@ -326,7 +326,9 @@ build_request(Method, Url, Headers, _Body) when Method =:= 'options'; Method =:= 'get'; Method =:= 'head'; Method =:= 'trace' -> - {kz_term:to_list(Url), ensure_string_headers(Headers)}; + {lists:flatten(kz_term:to_list(Url)) + ,ensure_string_headers(Headers) + }; build_request(Method, Url, Headers, Body) when Method =:= 'post'; Method =:= 'put'; Method =:= 'patch'; @@ -341,7 +343,7 @@ build_request(Method, Url, Headers, Body) when Method =:= 'post'; ,Headers ,"" ), - {kz_term:to_list(Url) + {lists:flatten(kz_term:to_list(Url)) ,ensure_string_headers(Headers) ,kz_term:to_list(ContentType) ,kz_term:to_binary(Body)