diff --git a/map/booking_availability_filter.cpp b/map/booking_availability_filter.cpp index 46965984b8..d511275ded 100644 --- a/map/booking_availability_filter.cpp +++ b/map/booking_availability_filter.cpp @@ -205,6 +205,8 @@ void AvailabilityFilter::UpdateParams(ParamsBase const & apiParams) void AvailabilityFilter::GetFeaturesFromCache(search::Results const & results, std::vector & sortedResults) { + sortedResults.clear(); + std::vector features; for (auto const & r : results) diff --git a/map/booking_filter_processor.cpp b/map/booking_filter_processor.cpp index ea285e439b..58d21821b0 100644 --- a/map/booking_filter_processor.cpp +++ b/map/booking_filter_processor.cpp @@ -40,16 +40,23 @@ void FilterProcessor::OnParamsUpdated(Type const type, }); } -void FilterProcessor::GetFeaturesFromCache(Type const type, search::Results const & results, +void FilterProcessor::GetFeaturesFromCache(Types const & types, search::Results const & results, FillSearchMarksCallback const & callback) { - GetPlatform().RunTask(Platform::Thread::File, [this, type, results, callback]() + GetPlatform().RunTask(Platform::Thread::File, [this, types, results, callback]() { - std::vector featuresSorted; - m_filters.at(type)->GetFeaturesFromCache(results, featuresSorted); + CachedResults cachedResults; + for (auto const type : types) + { + std::vector featuresSorted; + m_filters.at(type)->GetFeaturesFromCache(results, featuresSorted); - ASSERT(std::is_sorted(featuresSorted.begin(), featuresSorted.end()), ()); - callback(featuresSorted); + ASSERT(std::is_sorted(featuresSorted.begin(), featuresSorted.end()), ()); + + cachedResults.emplace_back(type, std::move(featuresSorted)); + } + + callback(std::move(cachedResults)); }); } diff --git a/map/booking_filter_processor.hpp b/map/booking_filter_processor.hpp index a356cef8bc..8bb6ac1477 100644 --- a/map/booking_filter_processor.hpp +++ b/map/booking_filter_processor.hpp @@ -25,8 +25,23 @@ class Api; namespace filter { + +struct CachedResut +{ + CachedResut(Type type, std::vector && featuresSorted) + : m_type(type) + , m_featuresSorted(featuresSorted) + { + } + + Type m_type; + std::vector m_featuresSorted; +}; + +using CachedResults = std::vector; + using FillSearchMarksCallback = - platform::SafeCallback availableHotelsSorted)>; + platform::SafeCallback; class FilterProcessor : public FilterBase::Delegate { @@ -37,7 +52,7 @@ public: void OnParamsUpdated(Type const type, std::shared_ptr const & params); - void GetFeaturesFromCache(Type const type, search::Results const & results, + void GetFeaturesFromCache(Types const & types, search::Results const & results, FillSearchMarksCallback const & callback); // FilterInterface::Delegate overrides: diff --git a/map/framework.cpp b/map/framework.cpp index 24c6cdb475..4f5b070564 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -3135,37 +3135,40 @@ void Framework::ShowViewportSearchResults(bool clear, booking::filter::Types typ search::Results::ConstIter begin, search::Results::ConstIter end) { + using booking::filter::Type; + using booking::filter::CachedResults; + ASSERT(!types.empty(), ()); search::Results results; results.AddResultsNoChecks(begin, end); - for (auto const type : types) + + auto const fillCallback = [this, clear, results](CachedResults filtersResults) { - auto const fillCallback = [this, type, clear, results] (std::vector featuresSorted) + auto const postProcessing = [filtersResults = move(filtersResults)](SearchMarkPoint & mark) { - auto const postProcessing = [type, featuresSorted] (SearchMarkPoint & mark) + auto const & id = mark.GetFeatureID(); + + if (!id.IsValid()) + return; + + for (auto const filterResult : filtersResults) { - auto const & id = mark.GetFeatureID(); + auto const found = std::binary_search(filterResult.m_featuresSorted.cbegin(), + filterResult.m_featuresSorted.cend(), id); - if (!id.IsValid()) - return; - - auto const found = std::binary_search(featuresSorted.cbegin(), featuresSorted.cend(), id); - - using booking::filter::Type; - - switch (type) + switch (filterResult.m_type) { case Type::Deals: mark.SetSale(found); break; case Type::Availability: mark.SetPreparing(!found); break; } - }; - - FillSearchResultsMarks(clear, results.begin(), results.end(), postProcessing); + } }; - m_bookingFilterProcessor.GetFeaturesFromCache(type, results, fillCallback); - } + FillSearchResultsMarks(clear, results.begin(), results.end(), postProcessing); + }; + + m_bookingFilterProcessor.GetFeaturesFromCache(types, results, fillCallback); } void Framework::ClearViewportSearchResults() diff --git a/map/map_tests/booking_filter_test.cpp b/map/map_tests/booking_filter_test.cpp index ebc31db00f..2e4e1ac02b 100644 --- a/map/map_tests/booking_filter_test.cpp +++ b/map/map_tests/booking_filter_test.cpp @@ -4,6 +4,7 @@ #include "indexer/indexer_tests_support/test_with_custom_mwms.hpp" #include "map/booking_availability_filter.hpp" +#include "map/booking_filter_processor.hpp" #include "partners_api/booking_api.hpp" @@ -15,6 +16,7 @@ #include "storage/country_info_getter.hpp" +#include #include using namespace booking::filter; @@ -52,6 +54,11 @@ private: booking::Api m_api; }; +void InsertResult(search::Result r, search::Results & dst) +{ + dst.AddResult(std::move(r)); +} + UNIT_CLASS_TEST(TestMwmEnvironment, BookingFilter_AvailabilitySmoke) { AvailabilityFilter filter(*this); @@ -107,4 +114,135 @@ UNIT_CLASS_TEST(TestMwmEnvironment, BookingFilter_AvailabilitySmoke) TEST_EQUAL(filteredResults[i].GetFeatureID(), expectedResults[i].GetFeatureID(), ()); } } + +UNIT_CLASS_TEST(TestMwmEnvironment, BookingFilter_ProcessorSmoke) +{ + std::vector const kHotelIds = {"10622", "10623", "10624", "10625", "10626"}; + std::set const kAvailableHotels = {"10623", "10624", "10625"}; + std::set const kHotelsWithDeals = {"10622", "10624", "10626"}; + + BuildCountry("TestMwm", [&kHotelIds](TestMwmBuilder & builder) + { + TestPOI hotel1(m2::PointD(0.7, 0.7), "hotel 22", "en"); + hotel1.GetMetadata().Set(feature::Metadata::FMD_SPONSORED_ID, kHotelIds[0]); + builder.Add(hotel1); + + TestPOI hotel2(m2::PointD(0.8, 0.8), "hotel 23", "en"); + hotel2.GetMetadata().Set(feature::Metadata::FMD_SPONSORED_ID, kHotelIds[1]); + builder.Add(hotel2); + + TestPOI hotel3(m2::PointD(0.9, 0.9), "hotel 24", "en"); + hotel3.GetMetadata().Set(feature::Metadata::FMD_SPONSORED_ID, kHotelIds[2]); + builder.Add(hotel3); + + TestPOI hotel4(m2::PointD(1.0, 1.0), "hotel 25", "en"); + hotel4.GetMetadata().Set(feature::Metadata::FMD_SPONSORED_ID, kHotelIds[3]); + builder.Add(hotel4); + + TestPOI hotel5(m2::PointD(1.1, 1.1), "hotel 26", "en"); + hotel5.GetMetadata().Set(feature::Metadata::FMD_SPONSORED_ID, kHotelIds[4]); + builder.Add(hotel5); + }); + + std::set availableWithDeals; + for (auto const & available : kAvailableHotels) + { + if (kHotelsWithDeals.find(available) != kHotelsWithDeals.cend()) + availableWithDeals.insert(available); + } + + m2::RectD const rect(m2::PointD(0.5, 0.5), m2::PointD(1.5, 1.5)); + search::Results results; + results.AddResult({"suggest for testing", "suggest for testing"}); + search::Results expectedAvailabilityResults; + search::Results expectedDealsResults; + search::Results expectedAvailableWithDeals; + m_index.ForEachInRect( + [&](FeatureType & ft) { + search::Result::Metadata metadata; + metadata.m_isSponsoredHotel = true; + std::string name; + ft.GetName(StringUtf8Multilang::kDefaultCode, name); + search::Result result(ft.GetID(), ft.GetCenter(), name, "", "", 0, metadata); + InsertResult(result, results); + + auto const sponsoredId = ft.GetMetadata().Get(feature::Metadata::FMD_SPONSORED_ID); + + if (kAvailableHotels.find(sponsoredId) != kAvailableHotels.cend()) + InsertResult(result, expectedAvailabilityResults); + + if (kHotelsWithDeals.find(sponsoredId) != kHotelsWithDeals.cend()) + InsertResult(result, expectedDealsResults); + + if (availableWithDeals.find(sponsoredId) != availableWithDeals.cend()) + InsertResult(result, expectedAvailableWithDeals); + }, + rect, scales::GetUpperScale()); + + TasksInternal tasks; + ParamsInternal availabilityParams; + search::Results availabilityResults; + availabilityParams.m_apiParams = make_shared(); + availabilityParams.m_callback = [&availabilityResults](search::Results const & results) { + availabilityResults = results; + }; + + tasks.emplace_back(Type::Availability, std::move(availabilityParams)); + + ParamsInternal dealsParams; + search::Results dealsResults; + booking::AvailabilityParams p; + p.m_dealsOnly = true; + dealsParams.m_apiParams = make_shared(p); + dealsParams.m_callback = [&dealsResults](search::Results const & results) { + dealsResults = results; + testing::Notify(); + }; + + tasks.emplace_back(Type::Deals, std::move(dealsParams)); + + FilterProcessor processor(GetIndex(), GetApi()); + auto tasksCopy = tasks; + processor.ApplyFilters(results, std::move(tasksCopy), ApplyMode::Independent); + + testing::Wait(); + + TEST_NOT_EQUAL(availabilityResults.GetCount(), 0, ()); + TEST_EQUAL(availabilityResults.GetCount(), expectedAvailabilityResults.GetCount(), ()); + + for (size_t i = 0; i < availabilityResults.GetCount(); ++i) + { + TEST_EQUAL(availabilityResults[i].GetFeatureID(), + expectedAvailabilityResults[i].GetFeatureID(), ()); + } + + TEST_NOT_EQUAL(dealsResults.GetCount(), 0, ()); + TEST_EQUAL(dealsResults.GetCount(), expectedDealsResults.GetCount(), ()); + + for (size_t i = 0; i < dealsResults.GetCount(); ++i) + { + TEST_EQUAL(dealsResults[i].GetFeatureID(), expectedDealsResults[i].GetFeatureID(), ()); + } + + processor.ApplyFilters(results, std::move(tasks), ApplyMode::Consecutive); + + testing::Wait(); + + TEST_NOT_EQUAL(availabilityResults.GetCount(), 0, ()); + TEST_EQUAL(availabilityResults.GetCount(), expectedAvailabilityResults.GetCount(), ()); + + for (size_t i = 0; i < availabilityResults.GetCount(); ++i) + { + TEST_EQUAL(availabilityResults[i].GetFeatureID(), + expectedAvailabilityResults[i].GetFeatureID(), ()); + } + + TEST_NOT_EQUAL(dealsResults.GetCount(), 0, ()); + TEST_EQUAL(dealsResults.GetCount(), expectedAvailableWithDeals.GetCount(), ()); + + for (size_t i = 0; i < dealsResults.GetCount(); ++i) + { + TEST_EQUAL(dealsResults[i].GetFeatureID(), expectedAvailableWithDeals[i].GetFeatureID(), ()); + } +} } // namespace diff --git a/partners_api/booking_api.cpp b/partners_api/booking_api.cpp index fda5881e03..159be32f8c 100644 --- a/partners_api/booking_api.cpp +++ b/partners_api/booking_api.cpp @@ -65,20 +65,42 @@ std::string FormatTime(system_clock::time_point p) return partners_api::FormatTime(p, "%Y-%m-%d"); } -string MakeApiUrlV1(string const & func, url::Params const & params) +string MakeUrlForTesting(string const & func, url::Params const & params, string const & divider) +{ + ASSERT(!g_BookingUrlForTesting.empty(), ()); + + auto funcForTesting = func; + if (funcForTesting == "hotelAvailability") + { + auto const it = find_if(params.cbegin(), params.cend(), [](url::Param const & param) + { + return param.m_name == "show_only_deals"; + }); + + if (it != params.cend()) + funcForTesting = "deals"; + } + + return url::Make(g_BookingUrlForTesting + divider + funcForTesting, params); +} + +string MakeApiUrlImpl(string const & baseUrl, string const & func, url::Params const & params, + string const & divider) { if (!g_BookingUrlForTesting.empty()) - return url::Make(g_BookingUrlForTesting + "." + func, params); + return MakeUrlForTesting(func, params, divider); - return url::Make(kBookingApiBaseUrlV1 + "." + func, params); + return url::Make(baseUrl + divider + func, params); +} + +string MakeApiUrlV1(string const & func, url::Params const & params) +{ + return MakeApiUrlImpl(kBookingApiBaseUrlV1, func, params, "."); } string MakeApiUrlV2(string const & func, url::Params const & params) { - if (!g_BookingUrlForTesting.empty()) - return url::Make(g_BookingUrlForTesting + "/" + func, params); - - return url::Make(kBookingApiBaseUrlV2 + "/" + func, params); + return MakeApiUrlImpl(kBookingApiBaseUrlV2, func, params, "/"); } void ClearHotelInfo(HotelInfo & info) diff --git a/tools/python/ResponseProvider.py b/tools/python/ResponseProvider.py index a1e8c50418..550bd820ac 100644 --- a/tools/python/ResponseProvider.py +++ b/tools/python/ResponseProvider.py @@ -144,6 +144,7 @@ class ResponseProvider: "/booking/min_price": self.partners_minprice, "/booking/min_price.getHotelAvailability": self.partners_minprice, "/booking/min_price/hotelAvailability": self.partners_hotel_availability, + "/booking/min_price/deals": self.partners_hotels_with_deals, "/partners/taxi_info": self.partners_yandex_taxi_info, "/partners/get-offers-in-bbox/": self.partners_rent_nearby, "/partners/CalculateByCoords": self.partners_calculate_by_coords, @@ -228,6 +229,9 @@ class ResponseProvider: def partners_hotel_availability(self): return Payload(jsons.HOTEL_AVAILABILITY) + def partners_hotels_with_deals(self): + return Payload(jsons.HOTELS_WITH_DEALS) + def partners_yandex_taxi_info(self): return Payload(jsons.PARTNERS_TAXI_INFO) diff --git a/tools/python/jsons.py b/tools/python/jsons.py index 0637d77874..68f512b37c 100644 --- a/tools/python/jsons.py +++ b/tools/python/jsons.py @@ -208,6 +208,28 @@ HOTEL_AVAILABILITY = """ } """ +HOTELS_WITH_DEALS = """ +{ + "result": [ + { + "hotel_currency_code": "EUR", + "hotel_id": 10622, + "price": 801 + }, + { + "hotel_currency_code": "USD", + "hotel_id": 10624, + "price": 802 + }, + { + "hotel_currency_code": "RUR", + "hotel_id": 10626, + "price": 803 + } + ] +} +""" + PARTNERS_MINPRICE = """ [ {