From fa59cb2a6a97839d035f33127aff5e38d334a85a Mon Sep 17 00:00:00 2001 From: Arsentiy Milchakov Date: Tue, 21 May 2019 20:19:28 +0300 Subject: [PATCH] [core] cross reference api + tests + framework integration --- base/visitor.hpp | 6 + indexer/ftypes_sponsored.hpp | 1 + map/CMakeLists.txt | 2 + map/cross_reference_delegate.cpp | 31 ++ map/cross_reference_delegate.hpp | 26 ++ map/framework.cpp | 19 ++ map/framework.hpp | 3 + map/place_page_info.hpp | 3 +- metrics/eye.cpp | 52 +++- metrics/eye.hpp | 3 + metrics/eye_info.hpp | 25 +- metrics/metrics_tests/eye_tests.cpp | 5 + .../metrics_tests_support/eye_for_testing.hpp | 2 +- partners_api/CMakeLists.txt | 2 + partners_api/cross_reference_api.cpp | 241 +++++++++++++++ partners_api/cross_reference_api.hpp | 73 +++++ .../partners_api_tests/CMakeLists.txt | 4 + .../cross_reference_tests.cpp | 291 ++++++++++++++++++ tools/python/ResponseProvider.py | 4 + tools/python/jsons.py | 41 +++ 20 files changed, 825 insertions(+), 9 deletions(-) create mode 100644 map/cross_reference_delegate.cpp create mode 100644 map/cross_reference_delegate.hpp create mode 100644 partners_api/cross_reference_api.cpp create mode 100644 partners_api/cross_reference_api.hpp create mode 100644 partners_api/partners_api_tests/cross_reference_tests.cpp diff --git a/base/visitor.hpp b/base/visitor.hpp index d3c1c11970..3ac2bb8078 100644 --- a/base/visitor.hpp +++ b/base/visitor.hpp @@ -20,6 +20,12 @@ public: m_os << DebugPrint(t); } + template + void operator()(T const & t, T const & /* with optional value */, char const * name = nullptr) + { + operator()(t, name); + } + std::string ToString() const { return m_name + " [" + m_os.str() + "]"; } private: diff --git a/indexer/ftypes_sponsored.hpp b/indexer/ftypes_sponsored.hpp index d32fa2b0fc..44cad51818 100644 --- a/indexer/ftypes_sponsored.hpp +++ b/indexer/ftypes_sponsored.hpp @@ -21,6 +21,7 @@ protected: SPONSORED_CHECKER(IsBookingChecker, "booking"); SPONSORED_CHECKER(IsOpentableChecker, "opentable"); SPONSORED_CHECKER(IsHolidayChecker, "holiday"); +SPONSORED_CHECKER(IsCrossReferenceCityChecker, "cross_reference"); #undef SPONSORED_CHECKER } // namespace ftypes diff --git a/map/CMakeLists.txt b/map/CMakeLists.txt index e89a3f48e7..29b8a66f78 100644 --- a/map/CMakeLists.txt +++ b/map/CMakeLists.txt @@ -16,6 +16,8 @@ set( benchmark_tools.cpp booking_availability_filter.cpp booking_availability_filter.hpp + cross_reference_delegate.cpp + cross_reference_delegate.hpp booking_filter.hpp booking_filter_cache.cpp booking_filter_cache.hpp diff --git a/map/cross_reference_delegate.cpp b/map/cross_reference_delegate.cpp new file mode 100644 index 0000000000..2f259dc615 --- /dev/null +++ b/map/cross_reference_delegate.cpp @@ -0,0 +1,31 @@ +#include "map/cross_reference_delegate.hpp" + +#include "search/city_finder.hpp" + +#include "indexer/data_source.hpp" +#include "indexer/ftypes_sponsored.hpp" + +CrossReferenceDelegate::CrossReferenceDelegate(DataSource const & dataSource, + search::CityFinder & cityFinder) + : m_dataSource(dataSource) + , m_cityFinder(cityFinder) +{ +} + +std::string CrossReferenceDelegate::GetCityOsmId(m2::PointD const & point) +{ + auto const featureId = m_cityFinder.GetCityFeatureID(point); + + if (!featureId.IsValid()) + return {}; + + FeaturesLoaderGuard guard(m_dataSource, featureId.m_mwmId); + auto feature = guard.GetOriginalFeatureByIndex(featureId.m_index); + if (!feature) + return {}; + + if (ftypes::IsCrossReferenceCityChecker::Instance()(*feature)) + return feature->GetMetadata().Get(feature::Metadata::FMD_SPONSORED_ID); + + return {}; +} diff --git a/map/cross_reference_delegate.hpp b/map/cross_reference_delegate.hpp new file mode 100644 index 0000000000..48a6fa766c --- /dev/null +++ b/map/cross_reference_delegate.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "partners_api/cross_reference_api.hpp" + +#include "geometry/point2d.hpp" + +#include + +class DataSource; + +namespace search +{ + class CityFinder; +} + +class CrossReferenceDelegate : public cross_reference::Api::Delegate +{ +public: + CrossReferenceDelegate(DataSource const & dataSource, search::CityFinder & cityFinder); + + std::string GetCityOsmId(m2::PointD const & point) override; + +private: + DataSource const & m_dataSource; + search::CityFinder & m_cityFinder; +}; diff --git a/map/framework.cpp b/map/framework.cpp index bcf4c25c75..e4d390cce0 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -1,6 +1,7 @@ #include "map/framework.hpp" #include "map/benchmark_tools.hpp" #include "map/chart_generator.hpp" +#include "map/cross_reference_delegate.hpp" #include "map/displayed_categories_modifiers.hpp" #include "map/everywhere_search_params.hpp" #include "map/ge0_parser.hpp" @@ -575,6 +576,9 @@ Framework::Framework(FrameworkParams const & params) GetPowerManager().Subscribe(this); GetPowerManager().Load(); + + m_crossReferenceApi->SetDelegate(make_unique(m_model.GetDataSource(), + *m_cityFinder)); } Framework::~Framework() @@ -639,6 +643,15 @@ locals::Api * Framework::GetLocalsApi(platform::NetworkPolicy const & policy) return nullptr; } +cross_reference::Api * Framework::GetCrossReferenceApi(platform::NetworkPolicy const & policy) const +{ + ASSERT(m_crossReferenceApi, ()); + if (policy.CanUse()) + return m_crossReferenceApi.get(); + + return nullptr; +} + void Framework::ShowNode(storage::CountryId const & countryId) { StopLocationFollow(); @@ -955,6 +968,10 @@ void Framework::FillInfoFromFeatureType(FeatureType & ft, place_page::Info & inf { info.SetSponsoredType(place_page::SponsoredType::Holiday); } + else if (ftypes::IsCrossReferenceCityChecker::Instance()(ft)) + { + info.SetSponsoredType(SponsoredType::CrossReference); + } FillLocalExperts(ft, info); FillDescription(ft, info); @@ -1481,6 +1498,8 @@ void Framework::EnterForeground() m_trafficManager.OnEnterForeground(); m_routingManager.SetAllowSendingPoints(true); + + m_crossReferenceApi->OnEnterForeground(); } void Framework::InitCountryInfoGetter() diff --git a/map/framework.hpp b/map/framework.hpp index d4a135a53d..7f66e9fe5f 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -62,6 +62,7 @@ #include "partners_api/banner.hpp" #include "partners_api/booking_api.hpp" +#include "partners_api/cross_reference_api.hpp" #include "partners_api/locals_api.hpp" #include "partners_api/taxi_engine.hpp" @@ -229,6 +230,7 @@ protected: unique_ptr m_bookingApi = make_unique(); unique_ptr m_localsApi = make_unique(); + unique_ptr m_crossReferenceApi = make_unique(); df::DrapeApi m_drapeApi; @@ -276,6 +278,7 @@ public: booking::Api const * GetBookingApi(platform::NetworkPolicy const & policy) const; taxi::Engine * GetTaxiEngine(platform::NetworkPolicy const & policy); locals::Api * GetLocalsApi(platform::NetworkPolicy const & policy); + cross_reference::Api * GetCrossReferenceApi(platform::NetworkPolicy const & policy) const; ugc::Api * GetUGCApi() { return m_ugcApi.get(); } ugc::Api const * GetUGCApi() const { return m_ugcApi.get(); } diff --git a/map/place_page_info.hpp b/map/place_page_info.hpp index b747b0a605..3394d2fb65 100644 --- a/map/place_page_info.hpp +++ b/map/place_page_info.hpp @@ -45,7 +45,8 @@ enum class SponsoredType Booking, Opentable, Partner, - Holiday + Holiday, + CrossReference, }; enum class LocalAdsStatus diff --git a/metrics/eye.cpp b/metrics/eye.cpp index d538243150..388a4f0778 100644 --- a/metrics/eye.cpp +++ b/metrics/eye.cpp @@ -23,10 +23,14 @@ namespace auto constexpr kMapObjectEventsExpirePeriod = std::chrono::hours(24 * 30 * 3); auto constexpr kEventCooldown = std::chrono::seconds(2); -std::array const kMapEventSupportedTypes = {{"amenity-bar", "amenity-cafe", - "amenity-pub", "amenity-restaurant", - "amenity-fast_food", "amenity-biergarden", - "shop-bakery"}}; +std::array const kMapEventSupportedTypes = {{"amenity-bar", "amenity-cafe", + "amenity-pub", "amenity-restaurant", + "amenity-fast_food", "amenity-biergarden", + "shop-bakery", "tourism-hotel", + "tourism-apartment", "tourism-camp_site", + "tourism-chalet", "tourism-guest_house", + "tourism-hostel", "tourism-motel", + "tourism-resort", "sponsored-booking"}}; void Load(Info & info) { @@ -343,9 +347,10 @@ void Eye::RegisterMapObjectEvent(MapObject const & mapObject, MapObject::Event:: MapObject result = mapObject; MapObject::Event event; + auto const eventTime = Clock::now(); event.m_type = type; event.m_userPos = userPos; - event.m_eventTime = Clock::now(); + event.m_eventTime = eventTime; bool found = false; bool duplication = false; @@ -378,6 +383,14 @@ void Eye::RegisterMapObjectEvent(MapObject const & mapObject, MapObject::Event:: mapObjects.Add(result); } + if (type == MapObject::Event::Type::BookingBook || + type == MapObject::Event::Type::BookingMore || + type == MapObject::Event::Type::BookingReviews || + type == MapObject::Event::Type::BookingDetails) + { + editableInfo->m_crossReferences.m_transitionToBookingTime = eventTime; + } + if (!SaveLastMapObjectEvent(result)) return; @@ -391,6 +404,26 @@ void Eye::RegisterMapObjectEvent(MapObject const & mapObject, MapObject::Event:: }); } +void Eye::RegisterCrossReferenceAfterBookingShown() +{ + auto const info = m_info.Get(); + auto editableInfo = std::make_shared(*info); + auto const now = Clock::now(); + + editableInfo->m_crossReferences.m_lastTimeShownAfterBooking = now; + + if (!Save(editableInfo)) + return; + + GetPlatform().RunTask(Platform::Thread::Gui, [this, now] + { + for (auto subscriber : m_subscribers) + { + subscriber->OnCrossReferenceAfterBookingShown(now); + } + }); +} + // Eye::Event methods ------------------------------------------------------------------------------ // static void Eye::Event::TipClicked(Tip::Type type, Tip::Event event) @@ -470,4 +503,13 @@ void Eye::Event::MapObjectEvent(MapObject const & mapObject, MapObject::Event::T Instance().RegisterMapObjectEvent(mapObject, type, userPos); }); } + +// static +void Eye::Event::CrossReferenceAfterBookingShown() +{ + GetPlatform().RunTask(Platform::Thread::File, [] + { + Instance().RegisterCrossReferenceAfterBookingShown(); + }); +} } // namespace eye diff --git a/metrics/eye.hpp b/metrics/eye.hpp index cc9abb803f..65da09134e 100644 --- a/metrics/eye.hpp +++ b/metrics/eye.hpp @@ -24,6 +24,7 @@ public: virtual void OnDiscoveryItemClicked(Discovery::Event event) {} virtual void OnLayerShown(Layer const & layer) {} virtual void OnMapObjectEvent(MapObject const & poi) {} + virtual void OnCrossReferenceAfterBookingShown(Time const & time) {} }; // Note This class IS thread-safe. @@ -47,6 +48,7 @@ public: static void LayerShown(Layer::Type type); static void MapObjectEvent(MapObject const & mapObject, MapObject::Event::Type type, m2::PointD const & userPos); + static void CrossReferenceAfterBookingShown(); }; static Eye & Instance(); @@ -75,6 +77,7 @@ private: void RegisterLayerShown(Layer::Type type); void RegisterMapObjectEvent(MapObject const & mapObject, MapObject::Event::Type type, m2::PointD const & userPos); + void RegisterCrossReferenceAfterBookingShown(); base::AtomicSharedPtr m_info; std::vector m_subscribers; diff --git a/metrics/eye_info.hpp b/metrics/eye_info.hpp index e7725ec3fd..e8250ea0a4 100644 --- a/metrics/eye_info.hpp +++ b/metrics/eye_info.hpp @@ -183,7 +183,11 @@ public: AddToBookmark, UgcEditorOpened, UgcSaved, - RouteToCreated + RouteToCreated, + BookingBook, + BookingMore, + BookingReviews, + BookingDetails, }; DECLARE_VISITOR(visitor(m_type, "type"), visitor(m_userPos, "user_pos"), @@ -254,6 +258,17 @@ private: m2::RectD m_limitRect; }; +struct CrossReferences +{ + CrossReferences() = default; + + DECLARE_VISITOR_AND_DEBUG_PRINT(CrossReferences, + visitor(m_transitionToBookingTime, "transitionToBookingTime"), + visitor(m_lastTimeShownAfterBooking, "lastTimeShownAfterBooking")) + Time m_transitionToBookingTime; + Time m_lastTimeShownAfterBooking; +}; + using MapObjects = m4::Tree; struct InfoV0 @@ -262,7 +277,8 @@ struct InfoV0 DECLARE_VISITOR_AND_DEBUG_PRINT(InfoV0, visitor(m_booking, "booking"), visitor(m_bookmarks, "bookmarks"), visitor(m_discovery, "discovery"), visitor(m_layers, "layers"), - visitor(m_tips, "tips")) + visitor(m_tips, "tips"), + visitor(m_crossReferences, CrossReferences(), "crossReferences")) Booking m_booking; Bookmarks m_bookmarks; @@ -270,6 +286,7 @@ struct InfoV0 Layers m_layers; Tips m_tips; MapObjects m_mapObjects; + CrossReferences m_crossReferences; }; using Info = InfoV0; @@ -330,6 +347,10 @@ inline std::string DebugPrint(MapObject::Event::Type const & type) case MapObject::Event::Type::UgcEditorOpened: return "UgcEditorOpened"; case MapObject::Event::Type::UgcSaved: return "UgcSaved"; case MapObject::Event::Type::RouteToCreated: return "RouteToCreated"; + case MapObject::Event::Type::BookingBook: return "BookingBook"; + case MapObject::Event::Type::BookingMore: return "BookingMore"; + case MapObject::Event::Type::BookingReviews: return "BookingReviews"; + case MapObject::Event::Type::BookingDetails: return "BookingDetails"; } } } // namespace eye diff --git a/metrics/metrics_tests/eye_tests.cpp b/metrics/metrics_tests/eye_tests.cpp index f97e23ffaa..364c8e5062 100644 --- a/metrics/metrics_tests/eye_tests.cpp +++ b/metrics/metrics_tests/eye_tests.cpp @@ -54,8 +54,13 @@ Info MakeDefaultInfoForTesting() eventInfo.m_userPos = {53.016347, 158.683327}; eventInfo.m_type = MapObject::Event::Type::Open; poi.GetEditableEvents().push_back(eventInfo); + eventInfo.m_eventTime = Time(std::chrono::hours(100000)); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::BookingMore; info.m_mapObjects.Add(poi); + info.m_crossReferences.m_lastTimeShownAfterBooking = Time(std::chrono::hours(100000)); + return info; } diff --git a/metrics/metrics_tests_support/eye_for_testing.hpp b/metrics/metrics_tests_support/eye_for_testing.hpp index 0672acd0fe..a2e026a9ad 100644 --- a/metrics/metrics_tests_support/eye_for_testing.hpp +++ b/metrics/metrics_tests_support/eye_for_testing.hpp @@ -26,7 +26,7 @@ class ScopedEyeForTesting { public: ScopedEyeForTesting() { EyeForTesting::ResetEye(); } - ~ScopedEyeForTesting() { EyeForTesting::ResetEye(); } + virtual ~ScopedEyeForTesting() { EyeForTesting::ResetEye(); } private: Platform::ThreadRunner m_runner; diff --git a/partners_api/CMakeLists.txt b/partners_api/CMakeLists.txt index 97f8cccb03..11f2d00fb9 100644 --- a/partners_api/CMakeLists.txt +++ b/partners_api/CMakeLists.txt @@ -16,6 +16,8 @@ set( booking_block_params.cpp booking_block_params.hpp booking_params_base.hpp + cross_reference_api.cpp + cross_reference_api.hpp facebook_ads.cpp facebook_ads.hpp google_ads.cpp diff --git a/partners_api/cross_reference_api.cpp b/partners_api/cross_reference_api.cpp new file mode 100644 index 0000000000..99c69986bc --- /dev/null +++ b/partners_api/cross_reference_api.cpp @@ -0,0 +1,241 @@ +#include "cross_reference_api.hpp" + +#include "metrics/eye.hpp" + +#include "platform/http_client.hpp" +#include "platform/platform.hpp" +#include "platform/preferred_languages.hpp" +#include "platform/settings.hpp" + +#include "base/assert.hpp" + +#include +#include +#include + +#include "3party/jansson/myjansson.hpp" + +using namespace cross_reference; + +using namespace std::chrono; + +namespace +{ +constexpr minutes kMinMinutesCountAfterBooking = minutes(5); +constexpr minutes kMaxMinutesCountAfterBooking = minutes(60); +constexpr hours kShowCrossReferenceNotRaterThan = hours(24); + +std::array const kSupportedBookingTypes = {{"tourism-hotel", "tourism-apartment", + "tourism-camp_site", "tourism-chalet", + "tourism-guest_house", "tourism-hostel", + "tourism-motel", "tourism-resort", + "sponsored-booking"}}; + +bool NeedToShowImpl(eye::Eye::InfoType const & eyeInfo) +{ + auto const timeSinceLastShown = + eye::Clock::now() - eyeInfo->m_crossReferences.m_lastTimeShownAfterBooking; + auto const timeSinceLastTransitionToBooking = + eye::Clock::now() - eyeInfo->m_crossReferences.m_transitionToBookingTime; + + return timeSinceLastTransitionToBooking >= kMinMinutesCountAfterBooking || + timeSinceLastTransitionToBooking <= kMaxMinutesCountAfterBooking || + timeSinceLastShown > kShowCrossReferenceNotRaterThan; +} + +void ParseCityGallery(std::string const & src, cross_reference::CityGallery & result) +{ + base::Json root(src.c_str()); + auto const dataArray = json_object_get(root.get(), "data"); + + auto const size = json_array_size(dataArray); + + result.reserve(size); + for (size_t i = 0; i < size; ++i) + { + cross_reference::CityGalleryItem item; + auto const obj = json_array_get(dataArray, i); + FromJSONObject(obj, "name", item.m_name); + FromJSONObject(obj, "url", item.m_url); + + auto const imageUrlObj = json_object_get(obj, "image_url"); + if (!json_is_null(imageUrlObj)) + FromJSON(imageUrlObj, item.m_imageUrl); + + FromJSONObject(obj, "access", item.m_access); + + auto const tierObj = json_object_get(obj, "tier"); + if (!json_is_null(tierObj)) + FromJSON(tierObj, item.m_tier); + + auto const authorObj = json_object_get(obj, "author"); + FromJSONObject(authorObj, "key_id", item.m_author.m_id); + FromJSONObject(authorObj, "name", item.m_author.m_name); + + auto const luxCategoryObj = json_object_get(obj, "lux_category"); + + auto const luxCategoryNameobj = json_object_get(luxCategoryObj, "name"); + if (!json_is_null(luxCategoryNameobj)) + FromJSON(luxCategoryNameobj, item.m_luxCategory.m_name); + + FromJSONObject(luxCategoryObj, "color", item.m_luxCategory.m_color); + + result.emplace_back(std::move(item)); + } +} + +std::string MakeCityGalleryUrl(std::string const & baseUrl, std::string const & osmId, + std::string const & lang) +{ + return baseUrl + osmId + "/?lang=" + lang; +} + +void GetCrossReferenceCityGalleryImpl(std::string const & baseUrl, std::string const & osmId, + CityGalleryCallback const & cb) +{ + if (osmId.empty()) + { + GetPlatform().RunTask(Platform::Thread::Gui, [cb]() { cb({}); }); + return; + } + + CityGallery result; + std::string httpResult; + if (!WebApi::GetCityGalleryByOsmId(baseUrl, osmId, languages::GetCurrentNorm(), httpResult)) + { + GetPlatform().RunTask(Platform::Thread::Gui, [cb]() { cb({}); }); + return; + } + + try + { + ParseCityGallery(httpResult, result); + } + catch (base::Json::Exception const & e) + { + LOG(LERROR, (e.Msg())); + result.clear(); + } + + GetPlatform().RunTask(Platform::Thread::Gui, [ cb, result = move(result) ]() { cb(result); }); +} +} // namespace + +namespace cross_reference +{ +// static +bool WebApi::GetCityGalleryByOsmId(std::string const & baseUrl, std::string const & osmId, + std::string const & lang, std::string & result) +{ + platform::HttpClient request(MakeCityGalleryUrl(baseUrl, osmId, lang)); + return request.RunHttpRequest(result); +} + +Api::Api(std::string const & baseUrl /* = "https://routes.maps.me/gallery/v1/city/" */) + : m_baseUrl(baseUrl) +{ +} + +void Api::SetDelegate(std::unique_ptr delegate) +{ + m_delegate = std::move(delegate); +} + +void Api::OnEnterForeground() +{ + settings::TryGet("BookingCrossReferenceIsAwaiting", m_bookingCrossReferenceIsAwaiting); + + if (!m_bookingCrossReferenceIsAwaiting) + return; + + auto const eyeInfo = eye::Eye::Instance().GetInfo(); + auto const timeSinceLastTransitionToBooking = + eye::Clock::now() - eyeInfo->m_crossReferences.m_transitionToBookingTime; + + if (timeSinceLastTransitionToBooking < kMinMinutesCountAfterBooking || + timeSinceLastTransitionToBooking > kMaxMinutesCountAfterBooking) + { + m_bookingCrossReferenceIsAwaiting = false; + settings::Set("BookingCrossReferenceIsAwaiting", false); + } +} + +bool Api::NeedToShow() const +{ + if (!m_bookingCrossReferenceIsAwaiting) + return false; + + return NeedToShowImpl(eye::Eye::Instance().GetInfo()); +} + +void Api::GetCrossReferenceLinkAfterBooking(AfterBookingCallback const & cb) const +{ + CHECK(m_delegate, ()); + + auto const eyeInfo = eye::Eye::Instance().GetInfo(); + + if (!m_bookingCrossReferenceIsAwaiting || !NeedToShowImpl(eyeInfo)) + { + GetPlatform().RunTask(Platform::Thread::Gui, [cb]() { cb({}); }); + return; + } + + GetPlatform().RunTask(Platform::Thread::Background, [this, eyeInfo, cb]() + { + auto const targetTime = eyeInfo->m_crossReferences.m_transitionToBookingTime; + m2::PointD pos; + auto const found = + eyeInfo->m_mapObjects.FindNode([&pos, targetTime](eye::MapObject const & mapObject) + { + if (mapObject.GetEvents().empty()) + return false; + + auto const typeIt = std::find(kSupportedBookingTypes.cbegin(), + kSupportedBookingTypes.cend(), mapObject.GetBestType()); + + if (typeIt == kSupportedBookingTypes.cend()) + return false; + + for (auto const & event : mapObject.GetEvents()) + { + switch (event.m_type) + { + case eye::MapObject::Event::Type::BookingBook: + case eye::MapObject::Event::Type::BookingMore: + case eye::MapObject::Event::Type::BookingReviews: + case eye::MapObject::Event::Type::BookingDetails: + { + if (event.m_eventTime == targetTime) + { + pos = mapObject.GetPos(); + return true; + } + } + default: continue; + } + } + return false; + }); + + auto const osmId = found ? m_delegate->GetCityOsmId(pos) : ""; + auto const resultUrl = + osmId.empty() ? "" : MakeCityGalleryUrl(m_baseUrl, osmId, languages::GetCurrentNorm()); + + GetPlatform().RunTask(Platform::Thread::Gui, [cb, resultUrl]() { cb(resultUrl); }); + }); +} + +void Api::GetCrossReferenceCityGallery(std::string const & osmId, + CityGalleryCallback const & cb) const +{ + GetCrossReferenceCityGalleryImpl(m_baseUrl, osmId, cb); +} + +void Api::GetCrossReferenceCityGallery(m2::PointD const & point, + CityGalleryCallback const & cb) const +{ + CHECK(m_delegate, ()); + + GetCrossReferenceCityGalleryImpl(m_baseUrl, m_delegate->GetCityOsmId(point), cb); +} +} // namespace cross_reference diff --git a/partners_api/cross_reference_api.hpp b/partners_api/cross_reference_api.hpp new file mode 100644 index 0000000000..33f0dec148 --- /dev/null +++ b/partners_api/cross_reference_api.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "geometry/point2d.hpp" + +#include +#include +#include + +namespace cross_reference +{ +struct Author +{ + std::string m_id; + std::string m_name; +}; +struct LuxCategory +{ + std::string m_name; + std::string m_color; +}; + +struct CityGalleryItem +{ + std::string m_name; + std::string m_url; + std::string m_imageUrl; + std::string m_access; + std::string m_tier; + Author m_author; + LuxCategory m_luxCategory; +}; + +using CityGallery = std::vector; + +class WebApi +{ +public: + static bool GetCityGalleryByOsmId(std::string const & baseUrl, std::string const & osmId, + std::string const & lang, std::string & result); +}; + +using AfterBookingCallback = std::function; +using CityGalleryCallback = std::function; + +class Api +{ +public: + class Delegate + { + public: + virtual ~Delegate() = default; + + virtual std::string GetCityOsmId(m2::PointD const & point) = 0; + }; + + explicit Api(std::string const & baseUrl = "https://routes.maps.me/gallery/v1/city/"); + + void SetDelegate(std::unique_ptr delegate); + void OnEnterForeground(); + bool NeedToShow() const; + void GetCrossReferenceLinkAfterBooking(AfterBookingCallback const & cb) const; + void GetCrossReferenceCityGallery(std::string const & osmId, + CityGalleryCallback const & cb) const; + void GetCrossReferenceCityGallery(m2::PointD const & point, + CityGalleryCallback const & cb) const; + +private: + std::unique_ptr m_delegate; + + std::string m_baseUrl; + bool m_bookingCrossReferenceIsAwaiting = false; +}; +} // namespace cross_reference diff --git a/partners_api/partners_api_tests/CMakeLists.txt b/partners_api/partners_api_tests/CMakeLists.txt index 163b0f3f2d..5916607e7c 100644 --- a/partners_api/partners_api_tests/CMakeLists.txt +++ b/partners_api/partners_api_tests/CMakeLists.txt @@ -6,6 +6,7 @@ set( SRC ads_engine_tests.cpp booking_tests.cpp + cross_reference_tests.cpp facebook_tests.cpp google_tests.cpp maxim_tests.cpp @@ -23,7 +24,10 @@ omim_add_test(${PROJECT_NAME} ${SRC}) omim_link_libraries( ${PROJECT_NAME} + platform_tests_support + metrics_tests_support partners_api + metrics storage indexer platform diff --git a/partners_api/partners_api_tests/cross_reference_tests.cpp b/partners_api/partners_api_tests/cross_reference_tests.cpp new file mode 100644 index 0000000000..1d48e86640 --- /dev/null +++ b/partners_api/partners_api_tests/cross_reference_tests.cpp @@ -0,0 +1,291 @@ +#include "testing/testing.hpp" + +#include "partners_api/cross_reference_api.hpp" + +#include "platform/settings.hpp" + +#include "metrics/metrics_tests_support/eye_for_testing.hpp" + +#include "platform/platform_tests_support/async_gui_thread.hpp" + +#include +#include + +using namespace eye; +using namespace platform::tests_support; + +namespace +{ +std::string const kTestOsmId = "TestOsmId"; + +class ScopedEyeWithAsyncGuiThread : public AsyncGuiThread +{ +public: + ScopedEyeWithAsyncGuiThread() + { + EyeForTesting::ResetEye(); + } + + ~ScopedEyeWithAsyncGuiThread() override + { + EyeForTesting::ResetEye(); + } +}; + +class DelegateForTesting : public cross_reference::Api::Delegate +{ +public: + std::string GetCityOsmId(m2::PointD const &) override { return kTestOsmId; } +}; +} // namespace + +UNIT_CLASS_TEST(ScopedEyeWithAsyncGuiThread, CrossReference_NeedToShow) +{ + cross_reference::Api api; + Info info; + { + MapObject poi; + poi.SetBestType("tourism-hotel"); + poi.SetPos({53.652007, 108.143443}); + MapObject::Event eventInfo; + + eventInfo.m_eventTime = Clock::now() - std::chrono::hours((24 * 30 * 3) + 1); + eventInfo.m_userPos = {72.045507, 81.408095}; + eventInfo.m_type = MapObject::Event::Type::Open; + poi.GetEditableEvents().emplace_back(eventInfo); + + eventInfo.m_eventTime = + Clock::now() - (std::chrono::hours(24 * 30 * 3) + std::chrono::seconds(1)); + eventInfo.m_userPos = {72.045400, 81.408200}; + eventInfo.m_type = MapObject::Event::Type::AddToBookmark; + poi.GetEditableEvents().emplace_back(eventInfo); + + eventInfo.m_eventTime = Clock::now() - std::chrono::hours(24 * 30 * 3); + eventInfo.m_userPos = {72.045450, 81.408201}; + eventInfo.m_type = MapObject::Event::Type::RouteToCreated; + poi.GetEditableEvents().emplace_back(eventInfo); + + info.m_mapObjects.Add(poi); + } + + EyeForTesting::SetInfo(info); + settings::Set("BookingCrossReferenceIsAwaiting", true); + api.OnEnterForeground(); + TEST_EQUAL(api.NeedToShow(), false, ()); + + { + MapObject poi; + poi.SetBestType("tourism-hotel"); + poi.SetPos({53.652005, 108.143448}); + MapObject::Event eventInfo; + + eventInfo.m_eventTime = Clock::now() - std::chrono::hours(24 * 30 * 3); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::Open; + poi.GetEditableEvents().emplace_back(eventInfo); + + eventInfo.m_eventTime = Clock::now() - std::chrono::hours(2); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::BookingBook; + poi.GetEditableEvents().emplace_back(eventInfo); + + info.m_mapObjects.Add(poi); + } + + info.m_crossReferences.m_transitionToBookingTime = Clock::now() - std::chrono::hours(2); + EyeForTesting::SetInfo(info); + settings::Set("BookingCrossReferenceIsAwaiting", true); + api.OnEnterForeground(); + TEST_EQUAL(api.NeedToShow(), false, ()); + + { + MapObject poi; + poi.SetBestType("tourism-hotel"); + poi.SetPos({53.653005, 108.143548}); + MapObject::Event eventInfo; + + eventInfo.m_eventTime = Clock::now() - std::chrono::hours(24 * 20 * 3); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::Open; + poi.GetEditableEvents().emplace_back(eventInfo); + + eventInfo.m_eventTime = Clock::now() - std::chrono::minutes(6); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::BookingReviews; + poi.GetEditableEvents().emplace_back(eventInfo); + + eventInfo.m_eventTime = Clock::now() - std::chrono::minutes(3); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::Open; + poi.GetEditableEvents().emplace_back(eventInfo); + + eventInfo.m_eventTime = Clock::now() - std::chrono::minutes(1); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::RouteToCreated; + poi.GetEditableEvents().emplace_back(eventInfo); + + info.m_mapObjects.Add(poi); + } + + info.m_crossReferences.m_transitionToBookingTime = Clock::now() - std::chrono::minutes(6); + EyeForTesting::SetInfo(info); + settings::Set("BookingCrossReferenceIsAwaiting", true); + api.OnEnterForeground(); + TEST_EQUAL(api.NeedToShow(), true, ()); +} + +UNIT_CLASS_TEST(ScopedEyeWithAsyncGuiThread, CrossReference_GetCrossReferenceLinkAfterBooking) +{ + cross_reference::Api api; + api.SetDelegate(std::make_unique()); + Info info; + { + MapObject poi; + poi.SetBestType("tourism-hotel"); + poi.SetPos({53.652007, 108.143443}); + MapObject::Event eventInfo; + + eventInfo.m_eventTime = Clock::now() - std::chrono::hours((24 * 30 * 3) + 1); + eventInfo.m_userPos = {72.045507, 81.408095}; + eventInfo.m_type = MapObject::Event::Type::Open; + poi.GetEditableEvents().emplace_back(eventInfo); + + eventInfo.m_eventTime = + Clock::now() - (std::chrono::hours(24 * 30 * 3) + std::chrono::seconds(1)); + eventInfo.m_userPos = {72.045400, 81.408200}; + eventInfo.m_type = MapObject::Event::Type::AddToBookmark; + poi.GetEditableEvents().emplace_back(eventInfo); + + eventInfo.m_eventTime = Clock::now() - std::chrono::hours(24 * 30 * 3); + eventInfo.m_userPos = {72.045450, 81.408201}; + eventInfo.m_type = MapObject::Event::Type::RouteToCreated; + poi.GetEditableEvents().emplace_back(eventInfo); + + info.m_mapObjects.Add(poi); + } + + EyeForTesting::SetInfo(info); + settings::Set("BookingCrossReferenceIsAwaiting", true); + api.OnEnterForeground(); + + std::string result{}; + api.GetCrossReferenceLinkAfterBooking([&result](std::string const & url) + { + result = url; + testing::Notify(); + }); + + testing::Wait(); + TEST(result.empty(), ()); + + auto eventTime = Clock::now() - std::chrono::hours(2); + { + MapObject poi; + poi.SetBestType("tourism-hotel"); + poi.SetPos({53.652005, 108.143448}); + MapObject::Event eventInfo; + + eventInfo.m_eventTime = Clock::now() - std::chrono::hours(24 * 30 * 3); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::Open; + poi.GetEditableEvents().emplace_back(eventInfo); + + eventInfo.m_eventTime = eventTime; + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::BookingBook; + poi.GetEditableEvents().emplace_back(eventInfo); + + info.m_mapObjects.Add(poi); + } + + info.m_crossReferences.m_transitionToBookingTime = Clock::now() - std::chrono::hours(2); + EyeForTesting::SetInfo(info); + settings::Set("BookingCrossReferenceIsAwaiting", true); + api.OnEnterForeground(); + + result = {}; + api.GetCrossReferenceLinkAfterBooking([&result](std::string const & url) + { + result = url; + testing::Notify(); + }); + + testing::Wait(); + TEST(result.empty(), ()); + + eventTime = Clock::now() - std::chrono::minutes(6); + { + MapObject poi; + poi.SetBestType("tourism-hotel"); + poi.SetPos({53.653005, 108.143548}); + MapObject::Event eventInfo; + + eventInfo.m_eventTime = Clock::now() - std::chrono::hours(24 * 20 * 3); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::Open; + poi.GetEditableEvents().emplace_back(eventInfo); + + eventInfo.m_eventTime = eventTime; + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::BookingReviews; + poi.GetEditableEvents().emplace_back(eventInfo); + + eventInfo.m_eventTime = Clock::now() - std::chrono::minutes(3); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::Open; + poi.GetEditableEvents().emplace_back(eventInfo); + + eventInfo.m_eventTime = Clock::now() - std::chrono::minutes(1); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::RouteToCreated; + poi.GetEditableEvents().emplace_back(eventInfo); + + info.m_mapObjects.Add(poi); + } + + info.m_crossReferences.m_transitionToBookingTime = eventTime; + EyeForTesting::SetInfo(info); + settings::Set("BookingCrossReferenceIsAwaiting", true); + api.OnEnterForeground(); + + result = {}; + api.GetCrossReferenceLinkAfterBooking([&result](std::string const & url) + { + result = url; + testing::Notify(); + }); + + testing::Wait(); + TEST_NOT_EQUAL(result.find(kTestOsmId, 0), std::string::npos, ()); +} + +UNIT_CLASS_TEST(ScopedEyeWithAsyncGuiThread, CrossReference_GetCrossReferenceCityGallery) +{ + {} + cross_reference::Api api("http://localhost:34568/gallery/city/"); + api.SetDelegate(std::make_unique()); + + { + cross_reference::CityGallery result{}; + api.GetCrossReferenceCityGallery(kTestOsmId, [&result](cross_reference::CityGallery const & gallery) + { + result = gallery; + testing::Notify(); + }); + + testing::Wait(); + TEST_EQUAL(result.size(), 2, ()); + } + { + cross_reference::CityGallery result{}; + m2::PointD pt; + api.GetCrossReferenceCityGallery(pt, [&result](cross_reference::CityGallery const & gallery) + { + result = gallery; + testing::Notify(); + }); + + testing::Wait(); + TEST_EQUAL(result.size(), 2, ()); + } +} diff --git a/tools/python/ResponseProvider.py b/tools/python/ResponseProvider.py index 0b8eb176c8..31569d6f3d 100644 --- a/tools/python/ResponseProvider.py +++ b/tools/python/ResponseProvider.py @@ -147,6 +147,7 @@ class ResponseProvider: "/partners/taxi_info": self.partners_yandex_taxi_info, "/partners/get-offers-in-bbox/": self.partners_rent_nearby, "/partners/CalculateByCoords": self.partners_calculate_by_coords, + "/gallery/city/TestOsmId/": self.cross_reference_gallery_city, }[url]() except: return self.test_404() @@ -239,6 +240,9 @@ class ResponseProvider: def partners_calculate_by_coords(self): return Payload(jsons.PARTNERS_CALCULATE_BY_COORDS) + def cross_reference_gallery_city(self): + return Payload(jsons.CROSS_REFERENCE_GALLERY_CITY) + def kill(self): logging.debug("Kill called in ResponseProvider") self.delegate.kill() diff --git a/tools/python/jsons.py b/tools/python/jsons.py index a8c0913b55..d4a541bb32 100644 --- a/tools/python/jsons.py +++ b/tools/python/jsons.py @@ -513,3 +513,44 @@ PARTNERS_CALCULATE_BY_COORDS = """ "PriceString": "244.00 ₽" } """ + +CROSS_REFERENCE_GALLERY_CITY = """ +{ + "data": [ + { + "url": "bundle/73af3f02-b8e3-4f60-8ef0-1c3c5cff43ca", + "name": "По Виа Рипетта до мавзолея Августа и Алтаря мира", + "author": { + "key_id": "00000000-0000-0000-0000-000000000000", + "name": "The Village" + }, + "image_url": "http://localhost:8000/images/73af3f02-b8e3-4f60-8ef0-1c3c5cff43ca.jpg", + "access": "public", + "lux_category": { + "name": "LUX", + "color": "666666" + }, + "tier": "price.tier" + }, + { + "url": "bundle/73af3f02-b8e3-4f60-8ef0-1c3c5cff43ca", + "name": "Полеты в метро", + "author": { + "key_id": "00000000-0000-0000-0000-000000000000", + "name": "Bmj" + }, + "image_url": null, + "access": "public", + "lux_category": { + "name": null, + "color": "666666" + }, + "tier": null + } + ], + "errors": [], + "meta": { + "more": "search?city=666" + } +} +"""