forked from organicmaps/organicmaps
review fixes
This commit is contained in:
parent
4c82857eae
commit
8dcb02460f
12 changed files with 236 additions and 35 deletions
|
@ -15,8 +15,9 @@ template <typename Container>
|
|||
class Matcher
|
||||
{
|
||||
public:
|
||||
typename Container::const_iterator
|
||||
Find(feature::TypesHolder const & types) const
|
||||
using ConstIterator = typename Container::const_iterator;
|
||||
|
||||
ConstIterator Find(feature::TypesHolder const & types) const
|
||||
{
|
||||
for (auto const t : types)
|
||||
{
|
||||
|
@ -34,7 +35,7 @@ public:
|
|||
return m_mapping.cend();
|
||||
}
|
||||
|
||||
bool IsValid(typename Container::const_iterator it) const
|
||||
bool IsValid(ConstIterator it) const
|
||||
{
|
||||
return it != m_mapping.cend();
|
||||
}
|
||||
|
@ -44,8 +45,8 @@ public:
|
|||
return IsValid(Find(types));
|
||||
}
|
||||
|
||||
template<typename ... Args>
|
||||
void Append(std::vector<std::vector<std::string>> const & types, Args ... args)
|
||||
template<typename TypesPaths, typename ... Args>
|
||||
void Append(TypesPaths const & types, Args const & ... args)
|
||||
{
|
||||
for (auto const & type : types)
|
||||
{
|
||||
|
@ -54,7 +55,7 @@ public:
|
|||
holder.Assign(classif().GetTypeByPath(type));
|
||||
ASSERT(Find(holder) == m_mapping.cend(), ("This type already exists", type));
|
||||
#endif
|
||||
m_mapping.emplace(classif().GetTypeByPath(type), std::forward<Args>(args)...);
|
||||
m_mapping.emplace(classif().GetTypeByPath(type), args...);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,8 +64,8 @@ private:
|
|||
};
|
||||
|
||||
template <typename Key, typename Value>
|
||||
using HashMap = Matcher<std::unordered_map<Key, Value>>;
|
||||
using HashMapMatcher = Matcher<std::unordered_map<Key, Value>>;
|
||||
|
||||
template <typename Key>
|
||||
using HashSet = Matcher<std::unordered_set<Key>>;
|
||||
using HashSetMatcher = Matcher<std::unordered_set<Key>>;
|
||||
} // namespace ftypes
|
||||
|
|
|
@ -45,6 +45,7 @@ set(
|
|||
gps_tracker.hpp
|
||||
local_ads_manager.cpp
|
||||
local_ads_manager.hpp
|
||||
local_ads_supported_types.cpp
|
||||
mwm_tree.cpp
|
||||
mwm_tree.hpp
|
||||
mwm_url.cpp
|
||||
|
|
|
@ -935,13 +935,20 @@ void Framework::FillInfoFromFeatureType(FeatureType const & ft, place_page::Info
|
|||
|
||||
if (m_localAdsManager.IsSupportedType(info.GetTypes()))
|
||||
{
|
||||
info.m_localAdsStatus = m_localAdsManager.Contains(ft.GetID())
|
||||
? place_page::LocalAdsStatus::Customer
|
||||
: place_page::LocalAdsStatus::Candidate;
|
||||
if (m_localAdsManager.Contains(ft.GetID()))
|
||||
{
|
||||
info.m_localAdsStatus = place_page::LocalAdsStatus::Customer;
|
||||
info.m_localAdsUrl = m_localAdsManager.GetShowStatisticUrl();
|
||||
}
|
||||
else
|
||||
{
|
||||
info.m_localAdsStatus = place_page::LocalAdsStatus::Candidate;
|
||||
info.m_localAdsUrl = m_localAdsManager.GetStartCompanyUrl();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
info.m_localAdsStatus = place_page::LocalAdsStatus::Unavailable;
|
||||
info.m_localAdsStatus = place_page::LocalAdsStatus::NotAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,10 @@ std::string const kServerUrl = LOCAL_ADS_SERVER_URL;
|
|||
std::string const kCampaignFile = "local_ads_campaigns.dat";
|
||||
std::string const kLocalAdsSymbolsFile = "local_ads_symbols.txt";
|
||||
auto constexpr kWWanUpdateTimeout = std::chrono::hours(12);
|
||||
// Dummy
|
||||
std::string const kStartCompanyUrl = "https://maps.me";
|
||||
// Dummy
|
||||
std::string const kShowStatisticUrl = "https://github.com/mapsme/omim/graphs/commit-activity";
|
||||
|
||||
void SerializeCampaign(FileWriter & writer, std::string const & countryName,
|
||||
LocalAdsManager::Timestamp const & ts,
|
||||
|
@ -142,11 +146,6 @@ std::vector<uint8_t> SerializeLocalAdsToJSON(std::list<local_ads::Event> const &
|
|||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
void FillExcludeTypes(ftypes::HashSet<uint32_t> & excludeTypes)
|
||||
{
|
||||
excludeTypes.Append({{"amenity", "bench"}});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
LocalAdsManager::LocalAdsManager(GetMwmsByRectFn const & getMwmsByRectFn,
|
||||
|
@ -179,10 +178,11 @@ void LocalAdsManager::Startup()
|
|||
return;
|
||||
m_isRunning = true;
|
||||
}
|
||||
FillSupportedTypes();
|
||||
|
||||
m_thread = threads::SimpleThread(&LocalAdsManager::ThreadRoutine, this);
|
||||
|
||||
m_statistics.Startup();
|
||||
FillExcludeTypes(m_excludeTypes);
|
||||
}
|
||||
|
||||
void LocalAdsManager::Teardown()
|
||||
|
@ -459,5 +459,15 @@ bool LocalAdsManager::Contains(FeatureID const & featureId) const
|
|||
|
||||
bool LocalAdsManager::IsSupportedType(feature::TypesHolder const & types) const
|
||||
{
|
||||
return !m_excludeTypes.Contains(types);
|
||||
return m_supportedTypes.Contains(types);
|
||||
}
|
||||
|
||||
std::string const & LocalAdsManager::GetStartCompanyUrl() const
|
||||
{
|
||||
return kStartCompanyUrl;
|
||||
}
|
||||
|
||||
std::string const & LocalAdsManager::GetShowStatisticUrl() const
|
||||
{
|
||||
return kShowStatisticUrl;
|
||||
}
|
||||
|
|
|
@ -59,6 +59,9 @@ public:
|
|||
bool Contains(FeatureID const & featureId) const;
|
||||
bool IsSupportedType(feature::TypesHolder const & types) const;
|
||||
|
||||
std::string const & GetStartCompanyUrl() const;
|
||||
std::string const & GetShowStatisticUrl() const;
|
||||
|
||||
private:
|
||||
enum class RequestType
|
||||
{
|
||||
|
@ -78,6 +81,8 @@ private:
|
|||
|
||||
void UpdateFeaturesCache(df::CustomSymbols const & symbols);
|
||||
|
||||
void FillSupportedTypes();
|
||||
|
||||
GetMwmsByRectFn m_getMwmsByRectFn;
|
||||
GetMwmIdByName m_getMwmIdByNameFn;
|
||||
|
||||
|
@ -105,5 +110,5 @@ private:
|
|||
std::set<FeatureID> m_featuresCache;
|
||||
mutable std::mutex m_featuresCacheMutex;
|
||||
|
||||
ftypes::HashSet<uint32_t> m_excludeTypes;
|
||||
ftypes::HashSetMatcher<uint32_t> m_supportedTypes;
|
||||
};
|
||||
|
|
170
map/local_ads_supported_types.cpp
Normal file
170
map/local_ads_supported_types.cpp
Normal file
|
@ -0,0 +1,170 @@
|
|||
#include "map/local_ads_manager.hpp"
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
void LocalAdsManager::FillSupportedTypes()
|
||||
{
|
||||
m_supportedTypes.Append<std::initializer_list<std::initializer_list<char const *>>>(
|
||||
{{"amenity", "atm"},
|
||||
{"amenity", "bank"},
|
||||
{"amenity", "bar"},
|
||||
{"amenity", "bbq"},
|
||||
{"amenity", "bicycle_parking"},
|
||||
{"amenity", "bicycle_rental"},
|
||||
{"amenity", "brothel"},
|
||||
{"amenity", "bureau_de_change"},
|
||||
{"amenity", "cafe"},
|
||||
{"amenity", "car_rental"},
|
||||
{"amenity", "car_sharing"},
|
||||
{"amenity", "car_wash"},
|
||||
{"amenity", "casino"},
|
||||
{"amenity", "charging_station"},
|
||||
{"amenity", "childcare"},
|
||||
{"amenity", "cinema"},
|
||||
{"amenity", "clinic"},
|
||||
{"amenity", "college"},
|
||||
{"amenity", "community_centre"},
|
||||
{"amenity", "courthouse"},
|
||||
{"amenity", "dentist"},
|
||||
{"amenity", "doctors"},
|
||||
{"amenity", "fast_food"},
|
||||
{"amenity", "fuel"},
|
||||
{"amenity", "grave_yard"},
|
||||
{"amenity", "hospital"},
|
||||
{"amenity", "hunting_stand"},
|
||||
{"amenity", "kindergarten"},
|
||||
{"amenity", "library"},
|
||||
{"amenity", "marketplace"},
|
||||
{"amenity", "nightclub"},
|
||||
{"amenity", "parking"},
|
||||
{"amenity", "pharmacy"},
|
||||
{"amenity", "post_office"},
|
||||
{"amenity", "pub"},
|
||||
{"amenity", "public_bookcase"},
|
||||
{"amenity", "recycling"},
|
||||
{"amenity", "restaurant"},
|
||||
{"amenity", "school"},
|
||||
{"amenity", "shelter"},
|
||||
{"amenity", "taxi"},
|
||||
{"amenity", "telephone"},
|
||||
{"amenity", "theatre"},
|
||||
{"amenity", "toilets"},
|
||||
{"amenity", "townhall"},
|
||||
{"amenity", "university"},
|
||||
{"amenity", "vending_machine"},
|
||||
{"amenity", "veterinary"},
|
||||
{"amenity", "waste_disposal"},
|
||||
|
||||
{"craft", "brewery"},
|
||||
{"craft", "carpenter"},
|
||||
{"craft", "electrician"},
|
||||
{"craft", "gardener"},
|
||||
{"craft", "hvac"},
|
||||
{"craft", "metal_construction"},
|
||||
{"craft", "painter"},
|
||||
{"craft", "photographer"},
|
||||
{"craft", "plumber"},
|
||||
{"craft", "shoemaker"},
|
||||
{"craft", "tailor"},
|
||||
|
||||
{"historic", "castle"},
|
||||
{"historic", "museum"},
|
||||
{"historic", "ship"},
|
||||
|
||||
{"office", "company"},
|
||||
{"office", "estate_agent"},
|
||||
{"office", "government"},
|
||||
{"office", "lawyer"},
|
||||
{"office", "telecommunication"},
|
||||
|
||||
{"shop", "alcohol"},
|
||||
{"shop", "bakery"},
|
||||
{"shop", "beauty"},
|
||||
{"shop", "beverages"},
|
||||
{"shop", "bicycle"},
|
||||
{"shop", "bookmaker"},
|
||||
{"shop", "books"},
|
||||
{"shop", "butcher"},
|
||||
{"shop", "car"},
|
||||
{"shop", "car_parts"},
|
||||
{"shop", "car_repair"},
|
||||
{"shop", "chemist"},
|
||||
{"shop", "clothes"},
|
||||
{"shop", "computer"},
|
||||
{"shop", "confectionery"},
|
||||
{"shop", "convenience"},
|
||||
{"shop", "copyshop"},
|
||||
{"shop", "cosmetics"},
|
||||
{"shop", "department_store"},
|
||||
{"shop", "doityourself"},
|
||||
{"shop", "dry_cleaning"},
|
||||
{"shop", "electronics"},
|
||||
{"shop", "florist"},
|
||||
{"shop", "furniture"},
|
||||
{"shop", "garden_centre"},
|
||||
{"shop", "gift"},
|
||||
{"shop", "greengrocer"},
|
||||
{"shop", "hairdresser"},
|
||||
{"shop", "hardware"},
|
||||
{"shop", "jewelry"},
|
||||
{"shop", "kiosk"},
|
||||
{"shop", "laundry"},
|
||||
{"shop", "mall"},
|
||||
{"shop", "mobile_phone"},
|
||||
{"shop", "optician"},
|
||||
{"shop", "outdoor"},
|
||||
{"shop", "pet"},
|
||||
{"shop", "photo"},
|
||||
{"shop", "seafood"},
|
||||
{"shop", "shoes"},
|
||||
{"shop", "sports"},
|
||||
{"shop", "supermarket"},
|
||||
{"shop", "ticket"},
|
||||
{"shop", "toys"},
|
||||
{"shop", "travel_agency"},
|
||||
{"shop", "tyres"},
|
||||
{"shop", "wine"},
|
||||
|
||||
{"sponsored", "booking"},
|
||||
|
||||
{"sport", "american_football"},
|
||||
{"sport", "archery"},
|
||||
{"sport", "athletics"},
|
||||
{"sport", "australian_football"},
|
||||
{"sport", "baseball"},
|
||||
{"sport", "basketball"},
|
||||
{"sport", "bowls"},
|
||||
{"sport", "cricket"},
|
||||
{"sport", "curling"},
|
||||
{"sport", "diving"},
|
||||
{"sport", "equestrian"},
|
||||
{"sport", "football"},
|
||||
{"sport", "golf"},
|
||||
{"sport", "gymnastics"},
|
||||
{"sport", "handball"},
|
||||
{"sport", "multi"},
|
||||
{"sport", "scuba_diving"},
|
||||
{"sport", "shooting"},
|
||||
{"sport", "skiing"},
|
||||
{"sport", "soccer"},
|
||||
{"sport", "swimming"},
|
||||
{"sport", "tennis"},
|
||||
|
||||
{"tourism", "alpine_hut"},
|
||||
{"tourism", "apartment"},
|
||||
{"tourism", "artwork"},
|
||||
{"tourism", "attraction"},
|
||||
{"tourism", "camp_site"},
|
||||
{"tourism", "caravan_site"},
|
||||
{"tourism", "chalet"},
|
||||
{"tourism", "guest_house"},
|
||||
{"tourism", "hostel"},
|
||||
{"tourism", "hotel"},
|
||||
{"tourism", "information", "office"},
|
||||
{"tourism", "motel"},
|
||||
{"tourism", "museum"},
|
||||
{"tourism", "picnic_site"},
|
||||
{"tourism", "resort"},
|
||||
{"tourism", "viewpoint"},
|
||||
{"tourism", "zoo"}});
|
||||
}
|
|
@ -56,6 +56,7 @@ SOURCES += \
|
|||
gps_track_storage.cpp \
|
||||
gps_tracker.cpp \
|
||||
local_ads_manager.cpp \
|
||||
local_ads_supported_types.cpp \
|
||||
mwm_tree.cpp \
|
||||
mwm_url.cpp \
|
||||
place_page_info.cpp \
|
||||
|
|
|
@ -34,7 +34,7 @@ enum class SponsoredType
|
|||
|
||||
enum class LocalAdsStatus
|
||||
{
|
||||
Unavailable,
|
||||
NotAvailable,
|
||||
Candidate,
|
||||
Customer
|
||||
};
|
||||
|
@ -160,7 +160,7 @@ public:
|
|||
/// Ads source.
|
||||
ads::Engine * m_adsEngine = nullptr;
|
||||
|
||||
LocalAdsStatus m_localAdsStatus = LocalAdsStatus::Unavailable;
|
||||
LocalAdsStatus m_localAdsStatus = LocalAdsStatus::NotAvailable;
|
||||
string m_localAdsUrl;
|
||||
};
|
||||
} // namespace place_page
|
||||
|
|
|
@ -9,18 +9,20 @@ namespace ads
|
|||
{
|
||||
Container::Container() { AppendExcludedTypes({{"sponsored", "booking"}}); }
|
||||
|
||||
void Container::AppendEntry(std::vector<std::vector<std::string>> const & types,
|
||||
void Container::AppendEntry(std::initializer_list<std::initializer_list<char const *>> && types,
|
||||
std::string const & id)
|
||||
{
|
||||
m_typesToBanners.Append(types, id);
|
||||
m_typesToBanners.Append(std::move(types), id);
|
||||
}
|
||||
|
||||
void Container::AppendExcludedTypes(std::vector<std::vector<std::string>> const & types)
|
||||
void Container::AppendExcludedTypes(
|
||||
std::initializer_list<std::initializer_list<char const *>> && types)
|
||||
{
|
||||
m_excludedTypes.Append(types);
|
||||
m_excludedTypes.Append(std::move(types));
|
||||
}
|
||||
|
||||
void Container::AppendSupportedCountries(std::vector<storage::TCountryId> const & countries)
|
||||
void Container::AppendSupportedCountries(
|
||||
std::initializer_list<storage::TCountryId> const & countries)
|
||||
{
|
||||
m_supportedCountries.insert(countries.begin(), countries.end());
|
||||
}
|
||||
|
@ -33,7 +35,6 @@ bool Container::HasBanner(feature::TypesHolder const & types,
|
|||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !m_excludedTypes.Contains(types);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
#include "base/macros.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace feature
|
||||
{
|
||||
|
@ -47,13 +47,14 @@ public:
|
|||
std::string GetSearchBannerId() const override;
|
||||
|
||||
protected:
|
||||
void AppendEntry(std::vector<std::vector<std::string>> const & types, std::string const & id);
|
||||
void AppendExcludedTypes(std::vector<std::vector<std::string>> const & types);
|
||||
void AppendSupportedCountries(std::vector<storage::TCountryId> const & countries);
|
||||
void AppendEntry(std::initializer_list<std::initializer_list<char const *>> && types,
|
||||
std::string const & id);
|
||||
void AppendExcludedTypes(std::initializer_list<std::initializer_list<char const *>> && types);
|
||||
void AppendSupportedCountries(std::initializer_list<storage::TCountryId> const & countries);
|
||||
|
||||
private:
|
||||
ftypes::HashMap<uint32_t, std::string> m_typesToBanners;
|
||||
ftypes::HashSet<uint32_t> m_excludedTypes;
|
||||
ftypes::HashMapMatcher<uint32_t, std::string> m_typesToBanners;
|
||||
ftypes::HashSetMatcher<uint32_t> m_excludedTypes;
|
||||
// All countries are supported when empty.
|
||||
std::unordered_set<storage::TCountryId> m_supportedCountries;
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ auto const kEntertainmentPlacementId = "9";
|
|||
auto const kBuildingPlacementId = "11";
|
||||
auto const kBannerIdForOtherTypes = "14";
|
||||
|
||||
std::vector<storage::TCountryId> const kSupportedCountries =
|
||||
std::initializer_list<storage::TCountryId> const kSupportedCountries =
|
||||
{
|
||||
"Azerbaijan Region",
|
||||
"Armenia",
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
34921F661BFA0A6900737D6E /* api_mark_point.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 34921F611BFA0A6900737D6E /* api_mark_point.hpp */; };
|
||||
34DDA1811DBE5DF40088A609 /* libpartners_api.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 34DDA17F1DBE5DF40088A609 /* libpartners_api.a */; };
|
||||
34DDA1821DBE5DF40088A609 /* libtracking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 34DDA1801DBE5DF40088A609 /* libtracking.a */; };
|
||||
3D74ABBE1EA76F1D0063A898 /* local_ads_supported_types.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3D74ABBD1EA76F1D0063A898 /* local_ads_supported_types.cpp */; };
|
||||
45201E931CE4AC90008A4842 /* api_mark_point.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45201E921CE4AC90008A4842 /* api_mark_point.cpp */; };
|
||||
45580ABE1E2CBD5E00CD535D /* benchmark_tools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45580ABC1E2CBD5E00CD535D /* benchmark_tools.cpp */; };
|
||||
45580ABF1E2CBD5E00CD535D /* benchmark_tools.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 45580ABD1E2CBD5E00CD535D /* benchmark_tools.hpp */; };
|
||||
|
@ -139,6 +140,7 @@
|
|||
34AF87EA1DBE5AD000E5E7DC /* common-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "common-release.xcconfig"; path = "../common-release.xcconfig"; sourceTree = "<group>"; };
|
||||
34DDA17F1DBE5DF40088A609 /* libpartners_api.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libpartners_api.a; path = "/Users/igrechuhin/Repo/omim/xcode/partners_api/../../../omim-xcode-build/Debug/libpartners_api.a"; sourceTree = "<absolute>"; };
|
||||
34DDA1801DBE5DF40088A609 /* libtracking.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtracking.a; path = "/Users/igrechuhin/Repo/omim/xcode/tracking/../../../omim-xcode-build/Debug/libtracking.a"; sourceTree = "<absolute>"; };
|
||||
3D74ABBD1EA76F1D0063A898 /* local_ads_supported_types.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = local_ads_supported_types.cpp; sourceTree = "<group>"; };
|
||||
45201E921CE4AC90008A4842 /* api_mark_point.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = api_mark_point.cpp; sourceTree = "<group>"; };
|
||||
45580ABC1E2CBD5E00CD535D /* benchmark_tools.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = benchmark_tools.cpp; sourceTree = "<group>"; };
|
||||
45580ABD1E2CBD5E00CD535D /* benchmark_tools.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = benchmark_tools.hpp; sourceTree = "<group>"; };
|
||||
|
@ -413,6 +415,7 @@
|
|||
675345BD1A4054AD00A0A8C3 /* map */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3D74ABBD1EA76F1D0063A898 /* local_ads_supported_types.cpp */,
|
||||
56B6EAF61EA4BAF00037D963 /* mwm_tree.cpp */,
|
||||
56B6EAF71EA4BAF00037D963 /* mwm_tree.hpp */,
|
||||
45580ABD1E2CBD5E00CD535D /* benchmark_tools.hpp */,
|
||||
|
@ -620,6 +623,7 @@
|
|||
342D833A1D5233E8000D8AEA /* displacement_mode_manager.cpp in Sources */,
|
||||
45201E931CE4AC90008A4842 /* api_mark_point.cpp in Sources */,
|
||||
675346661A4054E800A0A8C3 /* ge0_parser.cpp in Sources */,
|
||||
3D74ABBE1EA76F1D0063A898 /* local_ads_supported_types.cpp in Sources */,
|
||||
0C2B73DE1E92AB9900530BB8 /* local_ads_manager.cpp in Sources */,
|
||||
F6B283071C1B03320081957A /* gps_track_storage.cpp in Sources */,
|
||||
6753463A1A4054E800A0A8C3 /* address_finder.cpp in Sources */,
|
||||
|
|
Loading…
Add table
Reference in a new issue