From 6b43e15a407420db171ade6b784bce475b2d35cd Mon Sep 17 00:00:00 2001 From: Moritz Haller Date: Sat, 14 Aug 2021 15:18:27 +0200 Subject: [PATCH] tmp --- components/brave_ads/test/BUILD.gn | 2 + vendor/bat-native-ads/BUILD.gn | 6 + .../ad_notification_serving.cc | 46 ++++- .../ad_notification_serving.h | 2 + .../ad_notification_serving_test.cc | 38 ++++ .../inline_content_ad_serving.cc | 68 ++++++- .../inline_content_ad_serving.h | 4 + .../ads/internal/ad_targeting/ad_targeting.cc | 28 +++ .../ads/internal/ad_targeting/ad_targeting.h | 4 + ...ative_inline_content_ads_database_table.cc | 115 ++++++++++++ ...eative_inline_content_ads_database_table.h | 10 ++ .../internal/eligible_ads/ad_features_info.cc | 25 +++ .../internal/eligible_ads/ad_features_info.h | 32 ++++ .../eligible_ad_notifications.cc | 84 ++++++++- .../eligible_ad_notifications.h | 22 +++ .../eligible_ad_notifications_unittest.cc | 144 +++++++++++++-- .../eligible_ads/eligible_ads_features.cc | 44 +++++ .../eligible_ads/eligible_ads_features.h | 25 +++ .../eligible_ads_features_unittest.cc | 53 ++++++ .../eligible_ads/eligible_ads_util.cc | 104 +++++++++++ .../internal/eligible_ads/eligible_ads_util.h | 168 ++++++++++++++++++ .../eligible_ads_util_unittest.cc | 108 +++++++++++ .../eligible_inline_content_ads.cc | 88 +++++++++ .../eligible_inline_content_ads.h | 25 +++ .../ad_serving/ad_serving_features.cc | 9 + .../features/ad_serving/ad_serving_features.h | 2 + .../ad_serving_features_unittest.cc | 11 ++ 27 files changed, 1253 insertions(+), 14 deletions(-) create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_features_info.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_features_info.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features_unittest.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util_unittest.cc diff --git a/components/brave_ads/test/BUILD.gn b/components/brave_ads/test/BUILD.gn index 954c096884d8..d7fdb1d554ca 100644 --- a/components/brave_ads/test/BUILD.gn +++ b/components/brave_ads/test/BUILD.gn @@ -66,6 +66,8 @@ source_set("brave_ads_unit_tests") { "//brave/vendor/bat-native-ads/src/bat/ads/internal/database/tables/segments_database_table_unittest.cc", "//brave/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications_issue_17199_unittest.cc", "//brave/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications_unittest.cc", + "//brave/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features_unittest.cc", + "//brave/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util_unittest.cc", "//brave/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads_unittest.cc", "//brave/vendor/bat-native-ads/src/bat/ads/internal/features/ad_rewards/ad_rewards_features_unittest.cc", "//brave/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features_unittest.cc", diff --git a/vendor/bat-native-ads/BUILD.gn b/vendor/bat-native-ads/BUILD.gn index 0adccd305ba3..5ef7e4f398e9 100644 --- a/vendor/bat-native-ads/BUILD.gn +++ b/vendor/bat-native-ads/BUILD.gn @@ -430,8 +430,14 @@ source_set("ads") { "src/bat/ads/internal/database/tables/geo_targets_database_table.h", "src/bat/ads/internal/database/tables/segments_database_table.cc", "src/bat/ads/internal/database/tables/segments_database_table.h", + "src/bat/ads/internal/eligible_ads/ad_features_info.cc", + "src/bat/ads/internal/eligible_ads/ad_features_info.h", "src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.cc", "src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.h", + "src/bat/ads/internal/eligible_ads/eligible_ads_features.cc", + "src/bat/ads/internal/eligible_ads/eligible_ads_features.h", + "src/bat/ads/internal/eligible_ads/eligible_ads_util.cc", + "src/bat/ads/internal/eligible_ads/eligible_ads_util.h", "src/bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads.cc", "src/bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads.h", "src/bat/ads/internal/eligible_ads/round_robin_ads.h", diff --git a/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving.cc b/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving.cc index db579771fe65..873982f0f419 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving.cc @@ -19,12 +19,14 @@ #include "bat/ads/internal/bundle/creative_ad_notification_info.h" #include "bat/ads/internal/client/client.h" #include "bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.h" +#include "bat/ads/internal/features/ad_serving/ad_serving_features.h" #include "bat/ads/internal/logging.h" #include "bat/ads/internal/p2a/p2a_ad_opportunities/p2a_ad_opportunity.h" #include "bat/ads/internal/platform/platform_helper.h" #include "bat/ads/internal/resources/frequency_capping/anti_targeting_resource.h" #include "bat/ads/internal/settings/settings.h" #include "bat/ads/internal/time_formatting_util.h" +#include "third_party/abseil-cpp/absl/types/optional.h" namespace ads { namespace ad_notifications { @@ -106,9 +108,21 @@ void AdServing::MaybeServeAd() { return; } - const SegmentList segments = ad_targeting_->GetSegments(); + int ad_serving_version = features::GetAdServingVersion(); + BLOG(1, "Ad serving version " << ad_serving_version); + if (ad_serving_version == 2) { + MaybeServeAdV2(); + return; + } + MaybeServeAdV1(); +} + +void AdServing::MaybeServeAdV1() { DCHECK(eligible_ads_); + + const SegmentList segments = ad_targeting_->GetSegments(); + eligible_ads_->GetForSegments( segments, [=](const bool was_allowed, const CreativeAdNotificationList& ads) { @@ -139,6 +153,36 @@ void AdServing::MaybeServeAd() { }); } +void AdServing::MaybeServeAdV2() { + const SegmentList interest_segments = ad_targeting_->GetInterestSegments(); + const SegmentList intent_segments = ad_targeting_->GetIntentSegments(); + + eligible_ads_->GetForFeatures( + interest_segments, intent_segments, + [=](const bool was_allowed, + absl::optional ad) { + if (was_allowed) { + p2a::RecordAdOpportunityForSegments(AdType::kAdNotification, + interest_segments); + } + + if (!ad) { + BLOG(1, "Ad notification not served: No eligible ads found"); + FailedToServeAd(); + return; + } + + if (!ServeAd(ad.value())) { + BLOG(1, "Failed to serve ad notification"); + FailedToServeAd(); + return; + } + + BLOG(1, "Served ad notification"); + ServedAd(ad.value()); + }); +} + void AdServing::OnAdsPerHourChanged() { const int64_t ads_per_hour = settings::GetAdsPerHour(); BLOG(1, "Maximum ads per hour changed to " << ads_per_hour); diff --git a/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving.h b/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving.h index 43460b2887c2..0918fd7ee2fa 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving.h +++ b/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving.h @@ -47,6 +47,8 @@ class AdServing { void StopServingAdsAtRegularIntervals(); void MaybeServeAd(); + void MaybeServeAdV1(); + void MaybeServeAdV2(); void OnAdsPerHourChanged(); diff --git a/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving_test.cc b/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving_test.cc index 56992330edbd..704c3261fcda 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving_test.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving_test.cc @@ -5,12 +5,15 @@ #include "bat/ads/internal/ad_serving/ad_notifications/ad_notification_serving.h" +#include #include #include "base/guid.h" +#include "base/test/scoped_feature_list.h" #include "bat/ads/internal/ad_serving/ad_targeting/geographic/subdivision/subdivision_targeting.h" #include "bat/ads/internal/ad_targeting/ad_targeting.h" #include "bat/ads/internal/database/tables/creative_ad_notifications_database_table.h" +#include "bat/ads/internal/features/ad_serving/ad_serving_features.h" #include "bat/ads/internal/resources/frequency_capping/anti_targeting_resource.h" #include "bat/ads/internal/unittest_base.h" #include "bat/ads/internal/unittest_util.h" @@ -172,4 +175,39 @@ TEST_F(BatAdsAdNotificationServingTest, // Assert } +TEST_F(BatAdsAdNotificationServingTest, ServeAdWithAdServingVersion2) { + // Arrange + RecordUserActivityEvents(); + + CreativeAdNotificationList creative_ad_notifications; + CreativeAdNotificationInfo creative_ad_notification = + GetCreativeAdNotification(); + creative_ad_notifications.push_back(creative_ad_notification); + Save(creative_ad_notifications); + + AdTargeting ad_targeting; + ad_targeting::geographic::SubdivisionTargeting subdivision_targeting; + resource::AntiTargeting anti_targeting_resource; + ad_notifications::AdServing ad_serving(&ad_targeting, &subdivision_targeting, + &anti_targeting_resource); + + const char kAdServingVersion[] = "ad_serving_version"; + std::map kAdServingParameters; + kAdServingParameters[kAdServingVersion] = 2; + + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitWithFeaturesAndParameters( + {{features::kAdServing, kAdServingParameters}}, {}); + + // Act + EXPECT_CALL(*ads_client_mock_, + ShowNotification(DoesMatchCreativeInstanceId( + creative_ad_notification.creative_instance_id))) + .Times(1); + + ServeAd(); + + // Assert +} + } // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/inline_content_ads/inline_content_ad_serving.cc b/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/inline_content_ads/inline_content_ad_serving.cc index fd9356132e3f..0a1b36e76974 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/inline_content_ads/inline_content_ad_serving.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/inline_content_ads/inline_content_ad_serving.cc @@ -15,9 +15,11 @@ #include "bat/ads/internal/bundle/creative_inline_content_ad_info.h" #include "bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads.h" #include "bat/ads/internal/features/inline_content_ads/inline_content_ads_features.h" +#include "bat/ads/internal/features/ad_serving/ad_serving_features.h" #include "bat/ads/internal/logging.h" #include "bat/ads/internal/p2a/p2a_ad_opportunities/p2a_ad_opportunity.h" #include "bat/ads/internal/resources/frequency_capping/anti_targeting_resource.h" +#include "third_party/abseil-cpp/absl/types/optional.h" namespace ads { namespace inline_content_ads { @@ -50,8 +52,6 @@ void AdServing::RemoveObserver(InlineContentAdServingObserver* observer) { void AdServing::MaybeServeAd(const std::string& dimensions, GetInlineContentAdCallback callback) { - const SegmentList segments = ad_targeting_->GetSegments(); - InlineContentAdInfo inline_content_ad; if (!features::inline_content_ads::IsEnabled()) { @@ -59,7 +59,24 @@ void AdServing::MaybeServeAd(const std::string& dimensions, return; } + int ad_serving_version = features::GetAdServingVersion(); + BLOG(1, "Ad serving version " << ad_serving_version); + if (ad_serving_version == 2) { + MaybeServeAdV2(dimensions, callback); + return; + } + + MaybeServeAdV1(dimensions, callback); +} + +void AdServing::MaybeServeAdV1(const std::string& dimensions, + GetInlineContentAdCallback callback) { DCHECK(eligible_ads_); + + InlineContentAdInfo inline_content_ad; + + const SegmentList segments = ad_targeting_->GetSegments(); + eligible_ads_->GetForSegments( segments, dimensions, [=](const bool was_allowed, const CreativeInlineContentAdList& ads) { @@ -103,6 +120,53 @@ void AdServing::MaybeServeAd(const std::string& dimensions, }); } +void AdServing::MaybeServeAdV2(const std::string& dimensions, + GetInlineContentAdCallback callback) { + InlineContentAdInfo inline_content_ad; + + const SegmentList interest_segments = ad_targeting_->GetInterestSegments(); + const SegmentList intent_segments = ad_targeting_->GetIntentSegments(); + + eligible_ads_->GetForFeatures( + interest_segments, intent_segments, dimensions, + [=](const bool was_allowed, + absl::optional ad) { + if (!ad) { + BLOG(1, "Inline content ad not served: No eligible ads found"); + NotifyFailedToServeInlineContentAd(); + callback(/* success */ false, dimensions, inline_content_ad); + return; + } + + eligible_ads_->SetLastServedAd(ad.value()); + + InlineContentAdInfo inline_content_ad = + BuildInlineContentAd(ad.value()); + + BLOG(1, "Serving inline content ad:\n" + << " uuid: " << inline_content_ad.uuid << "\n" + << " creativeInstanceId: " + << inline_content_ad.creative_instance_id << "\n" + << " creativeSetId: " << inline_content_ad.creative_set_id + << "\n" + << " campaignId: " << inline_content_ad.campaign_id << "\n" + << " advertiserId: " << inline_content_ad.advertiser_id + << "\n" + << " segment: " << inline_content_ad.segment << "\n" + << " title: " << inline_content_ad.title << "\n" + << " description: " << inline_content_ad.description + << "\n" + << " imageUrl: " << inline_content_ad.image_url << "\n" + << " dimensions: " << inline_content_ad.dimensions << "\n" + << " ctaText: " << inline_content_ad.cta_text << "\n" + << " targetUrl: " << inline_content_ad.target_url); + + NotifyDidServeInlineContentAd(inline_content_ad); + + callback(/* success */ true, dimensions, inline_content_ad); + }); +} + /////////////////////////////////////////////////////////////////////////////// void AdServing::NotifyDidServeInlineContentAd( diff --git a/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/inline_content_ads/inline_content_ad_serving.h b/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/inline_content_ads/inline_content_ad_serving.h index c73364764e58..acd9f350cbd6 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/inline_content_ads/inline_content_ad_serving.h +++ b/vendor/bat-native-ads/src/bat/ads/internal/ad_serving/inline_content_ads/inline_content_ad_serving.h @@ -45,6 +45,10 @@ class AdServing { void MaybeServeAd(const std::string& dimensions, GetInlineContentAdCallback callback); + void MaybeServeAdV1(const std::string& dimensions, + GetInlineContentAdCallback callback); + void MaybeServeAdV2(const std::string& dimensions, + GetInlineContentAdCallback callback); private: AdTargeting* ad_targeting_; // NOT OWNED diff --git a/vendor/bat-native-ads/src/bat/ads/internal/ad_targeting/ad_targeting.cc b/vendor/bat-native-ads/src/bat/ads/internal/ad_targeting/ad_targeting.cc index 66d467d9dffe..e5e492fbd71a 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/ad_targeting/ad_targeting.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/ad_targeting/ad_targeting.cc @@ -48,4 +48,32 @@ SegmentList AdTargeting::GetSegments() const { return segments; } +SegmentList AdTargeting::GetInterestSegments() const { + SegmentList segments; + + if (features::IsTextClassificationEnabled()) { + const ad_targeting::model::TextClassification text_classification_model; + const SegmentList text_classification_segments = + text_classification_model.GetSegments(); + segments.insert(segments.end(), text_classification_segments.begin(), + text_classification_segments.end()); + } + + return segments; +} + +SegmentList AdTargeting::GetIntentSegments() const { + SegmentList segments; + + if (features::IsPurchaseIntentEnabled()) { + const ad_targeting::model::PurchaseIntent purchase_intent_model; + const SegmentList purchase_intent_segments = + purchase_intent_model.GetSegments(); + segments.insert(segments.end(), purchase_intent_segments.begin(), + purchase_intent_segments.end()); + } + + return segments; +} + } // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/ad_targeting/ad_targeting.h b/vendor/bat-native-ads/src/bat/ads/internal/ad_targeting/ad_targeting.h index 7b27201ff61a..d62560a91dce 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/ad_targeting/ad_targeting.h +++ b/vendor/bat-native-ads/src/bat/ads/internal/ad_targeting/ad_targeting.h @@ -17,6 +17,10 @@ class AdTargeting { ~AdTargeting(); SegmentList GetSegments() const; + + SegmentList GetInterestSegments() const; + + SegmentList GetIntentSegments() const; }; } // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/database/tables/creative_inline_content_ads_database_table.cc b/vendor/bat-native-ads/src/bat/ads/internal/database/tables/creative_inline_content_ads_database_table.cc index 9c514fe40878..50fc1e03c5a9 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/database/tables/creative_inline_content_ads_database_table.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/database/tables/creative_inline_content_ads_database_table.cc @@ -279,6 +279,100 @@ void CreativeInlineContentAds::GetForSegments( std::placeholders::_1, segments, callback)); } +void CreativeInlineContentAds::GetForDimensions( + const std::string& dimensions, + GetCreativeInlineContentAdsForDimensionsCallback callback) { + if (dimensions.empty()) { + callback(Result::SUCCESS, {}); + return; + } + + const std::string query = base::StringPrintf( + "SELECT " + "cbna.creative_instance_id, " + "cbna.creative_set_id, " + "cbna.campaign_id, " + "cam.start_at_timestamp, " + "cam.end_at_timestamp, " + "cam.daily_cap, " + "cam.advertiser_id, " + "cam.priority, " + "ca.conversion, " + "ca.per_day, " + "ca.per_week, " + "ca.per_month, " + "ca.total_max, " + "ca.split_test_group, " + "s.segment, " + "gt.geo_target, " + "ca.target_url, " + "cbna.title, " + "cbna.description, " + "cbna.image_url, " + "cbna.dimensions, " + "cbna.cta_text, " + "cam.ptr, " + "dp.dow, " + "dp.start_minute, " + "dp.end_minute " + "FROM %s AS cbna " + "INNER JOIN campaigns AS cam " + "ON cam.campaign_id = cbna.campaign_id " + "INNER JOIN segments AS s " + "ON s.creative_set_id = cbna.creative_set_id " + "INNER JOIN creative_ads AS ca " + "ON ca.creative_instance_id = cbna.creative_instance_id " + "INNER JOIN geo_targets AS gt " + "ON gt.campaign_id = cbna.campaign_id " + "INNER JOIN dayparts AS dp " + "ON dp.campaign_id = cbna.campaign_id " + "AND cbna.dimensions = '%s' " + "AND %s BETWEEN cam.start_at_timestamp AND cam.end_at_timestamp", + get_table_name().c_str(), dimensions.c_str(), + TimeAsTimestampString(base::Time::Now()).c_str()); + + DBCommandPtr command = DBCommand::New(); + command->type = DBCommand::Type::READ; + command->command = query; + + command->record_bindings = { + DBCommand::RecordBindingType::STRING_TYPE, // creative_instance_id + DBCommand::RecordBindingType::STRING_TYPE, // creative_set_id + DBCommand::RecordBindingType::STRING_TYPE, // campaign_id + DBCommand::RecordBindingType::INT64_TYPE, // start_at_timestamp + DBCommand::RecordBindingType::INT64_TYPE, // end_at_timestamp + DBCommand::RecordBindingType::INT_TYPE, // daily_cap + DBCommand::RecordBindingType::STRING_TYPE, // advertiser_id + DBCommand::RecordBindingType::INT_TYPE, // priority + DBCommand::RecordBindingType::BOOL_TYPE, // conversion + DBCommand::RecordBindingType::INT_TYPE, // per_day + DBCommand::RecordBindingType::INT_TYPE, // per_week + DBCommand::RecordBindingType::INT_TYPE, // per_month + DBCommand::RecordBindingType::INT_TYPE, // total_max + DBCommand::RecordBindingType::STRING_TYPE, // split_test_group + DBCommand::RecordBindingType::STRING_TYPE, // segment + DBCommand::RecordBindingType::STRING_TYPE, // geo_target + DBCommand::RecordBindingType::STRING_TYPE, // target_url + DBCommand::RecordBindingType::STRING_TYPE, // title + DBCommand::RecordBindingType::STRING_TYPE, // description + DBCommand::RecordBindingType::STRING_TYPE, // image_url + DBCommand::RecordBindingType::STRING_TYPE, // dimensions + DBCommand::RecordBindingType::STRING_TYPE, // cta_text + DBCommand::RecordBindingType::DOUBLE_TYPE, // ptr + DBCommand::RecordBindingType::STRING_TYPE, // dayparts->dow + DBCommand::RecordBindingType::INT_TYPE, // dayparts->start_minute + DBCommand::RecordBindingType::INT_TYPE // dayparts->end_minute + }; + + DBTransactionPtr transaction = DBTransaction::New(); + transaction->commands.push_back(std::move(command)); + + AdsClientHelper::Get()->RunDBTransaction( + std::move(transaction), + std::bind(&CreativeInlineContentAds::OnGetForDimensions, this, + std::placeholders::_1, callback)); +} + void CreativeInlineContentAds::GetAll( GetCreativeInlineContentAdsCallback callback) { const std::string query = base::StringPrintf( @@ -500,6 +594,27 @@ void CreativeInlineContentAds::OnGetForSegments( callback(Result::SUCCESS, segments, creative_inline_content_ads); } +void CreativeInlineContentAds::OnGetForDimensions( + DBCommandResponsePtr response, + GetCreativeInlineContentAdsForDimensionsCallback callback) { + if (!response || response->status != DBCommandResponse::Status::RESPONSE_OK) { + BLOG(0, "Failed to get creative inline content ads"); + callback(Result::FAILED, {}); + return; + } + + CreativeInlineContentAdList creative_inline_content_ads; + + for (const auto& record : response->result->get_records()) { + const CreativeInlineContentAdInfo creative_inline_content_ad = + GetFromRecord(record.get()); + + creative_inline_content_ads.push_back(creative_inline_content_ad); + } + + callback(Result::SUCCESS, creative_inline_content_ads); +} + void CreativeInlineContentAds::OnGetAll( DBCommandResponsePtr response, GetCreativeInlineContentAdsCallback callback) { diff --git a/vendor/bat-native-ads/src/bat/ads/internal/database/tables/creative_inline_content_ads_database_table.h b/vendor/bat-native-ads/src/bat/ads/internal/database/tables/creative_inline_content_ads_database_table.h index e9b8229584df..8cf67aac90cc 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/database/tables/creative_inline_content_ads_database_table.h +++ b/vendor/bat-native-ads/src/bat/ads/internal/database/tables/creative_inline_content_ads_database_table.h @@ -34,6 +34,10 @@ using GetCreativeInlineContentAdsCallback = const SegmentList& segments, const CreativeInlineContentAdList& ads)>; +using GetCreativeInlineContentAdsForDimensionsCallback = + std::function; + namespace database { namespace table { @@ -55,6 +59,9 @@ class CreativeInlineContentAds : public Table { const std::string& dimensions, GetCreativeInlineContentAdsCallback callback); + void GetForDimensions(const std::string& dimensions, + GetCreativeInlineContentAdsForDimensionsCallback callback); + void GetAll(GetCreativeInlineContentAdsCallback callback); void set_batch_size(const int batch_size); @@ -84,6 +91,9 @@ class CreativeInlineContentAds : public Table { const SegmentList& segments, GetCreativeInlineContentAdsCallback callback); + void OnGetForDimensions(DBCommandResponsePtr response, + GetCreativeInlineContentAdsForDimensionsCallback callback); + void OnGetAll(DBCommandResponsePtr response, GetCreativeInlineContentAdsCallback callback); diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_features_info.cc b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_features_info.cc new file mode 100644 index 000000000000..84791b7f8ca2 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_features_info.cc @@ -0,0 +1,25 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/eligible_ads/ad_features_info.h" + +#include "bat/ads/internal/bundle/creative_ad_notification_info.h" // TODO(Moritz Haller): rather use forward decl? +#include "bat/ads/internal/bundle/creative_inline_content_ad_info.h" + +namespace ads { + +template +AdFeaturesInfo::AdFeaturesInfo() = default; + +template +AdFeaturesInfo::AdFeaturesInfo(const AdFeaturesInfo& info) = default; + +template +AdFeaturesInfo::~AdFeaturesInfo() = default; + +template struct AdFeaturesInfo; +template struct AdFeaturesInfo; + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_features_info.h b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_features_info.h new file mode 100644 index 000000000000..ddbd7bd33b83 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_features_info.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_VENDOR_BAT_NATIVE_ADS_SRC_BAT_ADS_INTERNAL_ELIGIBLE_ADS_AD_FEATURES_INFO +#define BRAVE_VENDOR_BAT_NATIVE_ADS_SRC_BAT_ADS_INTERNAL_ELIGIBLE_ADS_AD_FEATURES_INFO + +#include "bat/ads/internal/ad_targeting/ad_targeting_segment.h" + +namespace ads { + +template +struct AdFeaturesInfo { + AdFeaturesInfo(); + AdFeaturesInfo(const AdFeaturesInfo& info); + ~AdFeaturesInfo(); + + T creative_ad; // TODO(Moritz Haller): slicing won't work bc of return val for `SampleAd(ads)` + SegmentList segments; + bool does_match_intent_child_segments; + bool does_match_intent_parent_segments; + bool does_match_interest_child_segments; + bool does_match_interest_parent_segments; + int ad_last_seen_hours_ago = 0; + int advertiser_last_seen_hours_ago = 0; + double score = 0.0; +}; + +} // namespace ads + +#endif // BRAVE_VENDOR_BAT_NATIVE_ADS_SRC_BAT_ADS_INTERNAL_ELIGIBLE_ADS_AD_FEATURES_INFO diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.cc b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.cc index 02f9a2ff845e..9b2fab49e4db 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.cc @@ -5,7 +5,7 @@ #include "bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.h" -#include +#include #include #include "bat/ads/ad_notification_info.h" @@ -19,6 +19,8 @@ #include "bat/ads/internal/client/client.h" #include "bat/ads/internal/database/tables/ad_events_database_table.h" #include "bat/ads/internal/database/tables/creative_ad_notifications_database_table.h" +#include "bat/ads/internal/eligible_ads/ad_features_info.h" +#include "bat/ads/internal/eligible_ads/eligible_ads_util.h" #include "bat/ads/internal/eligible_ads/seen_ads.h" #include "bat/ads/internal/eligible_ads/seen_advertisers.h" #include "bat/ads/internal/features/ad_serving/ad_serving_features.h" @@ -75,8 +77,88 @@ void EligibleAds::GetForSegments(const SegmentList& segments, }); } +// TODO(Moritz Haller): create ticket "dev concerns" in new issues. Not for now: +// abstract out v1/v2 code (get for *) into concrete class +void EligibleAds::GetForFeatures(const SegmentList& interest_segments, + const SegmentList& intent_segments, + SelectAdCallback callback) { // TODO(Moritz Haller): GetForFeaturesCallback + database::table::AdEvents database_table; + database_table.GetAll([=](const Result result, const AdEventList& ad_events) { + if (result != Result::SUCCESS) { + BLOG(1, "Failed to get ad events"); + callback(/* was_allowed */ false, absl::nullopt); + return; + } + + const int max_count = features::GetBrowsingHistoryMaxCount(); + const int days_ago = features::GetBrowsingHistoryDaysAgo(); + AdsClientHelper::Get()->GetBrowsingHistory( + max_count, days_ago, [=](const BrowsingHistoryList& history) { + GetEligibleAds(interest_segments, intent_segments, ad_events, history, + callback); + }); + }); +} + /////////////////////////////////////////////////////////////////////////////// +void EligibleAds::GetEligibleAds(const SegmentList& interest_segments, + const SegmentList& intent_segments, + const AdEventList& ad_events, + const BrowsingHistoryList& browsing_history, + SelectAdCallback callback) const { + BLOG(1, "Get eligible ads"); + + database::table::CreativeAdNotifications database_table; + database_table.GetAll([=](const Result result, const SegmentList& segments, + const CreativeAdNotificationList& ads) { + if (ads.empty()) { + BLOG(1, "No ads"); + callback(/* was_allowed */ true, absl::nullopt); + return; + } + + CreativeAdNotificationList eligible_ads = ApplyFrequencyCapping( + ads, + ShouldCapLastServedAd(ads) ? last_served_creative_ad_ + : CreativeAdInfo(), + ad_events, browsing_history); + + if (eligible_ads.empty()) { + BLOG(1, "No eligible ads"); + callback(/* was_allowed */ true, absl::nullopt); + return; + } + + ChooseAd(eligible_ads, ad_events, interest_segments, + intent_segments, callback); + }); +} + +void EligibleAds::ChooseAd( + const CreativeAdNotificationList& eligible_ads, + const AdEventList& ad_events, + const SegmentList& interest_segments, + const SegmentList& intent_segments, + SelectAdCallback callback) const { + DCHECK(!eligible_ads.empty()); + + std::map> ads = + GroupEligibleAdsByCreativeInstanceId(eligible_ads); + + // TODO(Moritz Haller): pass by value good here, or by ref? + std::map> + ads_with_features = ComputeFeaturesAndScores(ads, ad_events, + interest_segments, intent_segments); + + // TODO(Moritz Haller): "slicing" won't work bc of here, as + // we would have to down-cast again to concrete sub-class + const absl::optional ad = + SampleFromAds(ads_with_features); + + callback(/* was_allowed */ true, ad); +} + void EligibleAds::GetForParentChildSegments( const SegmentList& segments, const AdEventList& ad_events, diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.h b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.h index 910219869ebd..51b97961a2c2 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.h +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.h @@ -10,6 +10,7 @@ #include "bat/ads/internal/ad_targeting/ad_targeting_segment.h" #include "bat/ads/internal/bundle/creative_ad_notification_info.h" #include "bat/ads/internal/frequency_capping/frequency_capping_aliases.h" +#include "third_party/abseil-cpp/absl/types/optional.h" namespace ads { @@ -25,6 +26,10 @@ class AntiTargeting; namespace ad_notifications { +using SelectAdCallback = + std::function)>; + using GetEligibleAdsCallback = std::function; @@ -41,6 +46,10 @@ class EligibleAds { void GetForSegments(const SegmentList& segments, GetEligibleAdsCallback callback); + void GetForFeatures(const SegmentList& interest_segments, + const SegmentList& intent_segments, + SelectAdCallback callback); + private: ad_targeting::geographic::SubdivisionTargeting* subdivision_targeting_; // NOT OWNED @@ -49,6 +58,19 @@ class EligibleAds { CreativeAdInfo last_served_creative_ad_; + void GetEligibleAds(const SegmentList& interest_segments, + const SegmentList& intent_segments, + const AdEventList& ad_events, + const BrowsingHistoryList& browsing_history, + SelectAdCallback callback) const; + + void ChooseAd( + const CreativeAdNotificationList& eligible_ads, + const AdEventList& ad_events, + const SegmentList& interest_segments, + const SegmentList& intent_segments, + SelectAdCallback callback) const; + void GetForParentChildSegments(const SegmentList& segments, const AdEventList& ad_events, const BrowsingHistoryList& browsing_history, diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications_unittest.cc b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications_unittest.cc index 29f11265e80d..4dbaa518210e 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications_unittest.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications_unittest.cc @@ -5,15 +5,20 @@ #include "bat/ads/internal/eligible_ads/ad_notifications/eligible_ad_notifications.h" +#include #include #include #include "base/guid.h" +#include "base/test/scoped_feature_list.h" +#include "bat/ads/internal/ad_events/ad_events.h" #include "bat/ads/internal/ad_serving/ad_targeting/geographic/subdivision/subdivision_targeting.h" #include "bat/ads/internal/database/tables/creative_ad_notifications_database_table.h" +#include "bat/ads/internal/eligible_ads/eligible_ads_features.h" #include "bat/ads/internal/resources/frequency_capping/anti_targeting_resource.h" #include "bat/ads/internal/unittest_base.h" #include "bat/ads/internal/unittest_util.h" +#include "third_party/abseil-cpp/absl/types/optional.h" // npm run test -- brave_unit_tests --filter=BatAds* @@ -22,16 +27,11 @@ namespace ads { class BatAdsEligibleAdNotificationsTest : public UnitTestBase { protected: BatAdsEligibleAdNotificationsTest() - : database_table_( + : creative_ad_notifications_table_( std::make_unique()) {} ~BatAdsEligibleAdNotificationsTest() override = default; - void RecordUserActivityEvents() { - UserActivity::Get()->RecordEvent(UserActivityEventType::kOpenedNewTab); - UserActivity::Get()->RecordEvent(UserActivityEventType::kClosedTab); - } - CreativeAdNotificationInfo GetCreativeAdNotificationForSegment( const std::string& segment) { CreativeAdNotificationInfo creative_ad_notification; @@ -61,12 +61,32 @@ class BatAdsEligibleAdNotificationsTest : public UnitTestBase { } void Save(const CreativeAdNotificationList& creative_ad_notifications) { - database_table_->Save(creative_ad_notifications, [](const Result result) { - ASSERT_EQ(Result::SUCCESS, result); - }); + creative_ad_notifications_table_->Save( + creative_ad_notifications, + [](const Result result) { ASSERT_EQ(Result::SUCCESS, result); }); } - std::unique_ptr database_table_; + void Log(const CreativeAdNotificationInfo& creative_ad_notification, + int hours_ago) { + AdEventInfo ad_event; + ad_event.uuid = base::GenerateGUID(); + ad_event.type = AdType::kAdNotification; + ad_event.confirmation_type = ConfirmationType::kViewed; + ad_event.campaign_id = creative_ad_notification.campaign_id; + ad_event.creative_set_id = creative_ad_notification.creative_set_id; + ad_event.creative_instance_id = + creative_ad_notification.creative_instance_id; + ad_event.advertiser_id = creative_ad_notification.advertiser_id; + base::Time timestamp = + base::Time::Now() - base::TimeDelta::FromHours(hours_ago); + ad_event.timestamp = static_cast(timestamp.ToDoubleT()); + + LogAdEvent(ad_event, + [](const Result result) { ASSERT_EQ(Result::SUCCESS, result); }); + } + + std::unique_ptr + creative_ad_notifications_table_; }; TEST_F(BatAdsEligibleAdNotificationsTest, GetAdsForParentChildSegment) { @@ -265,4 +285,108 @@ TEST_F(BatAdsEligibleAdNotificationsTest, GetAdsForUnmatchedSegments) { // Assert } +TEST_F(BatAdsEligibleAdNotificationsTest, GetForFeaturesWithoutAds) { + // Arrange + const SegmentList intent_segments = {"intent-foo", "intent-bar"}; + const SegmentList interest_segments = {"interest-foo", "interest-bar"}; + + // Act + ad_targeting::geographic::SubdivisionTargeting subdivision_targeting; + resource::AntiTargeting anti_targeting_resource; + ad_notifications::EligibleAds eligible_ads(&subdivision_targeting, + &anti_targeting_resource); + + eligible_ads.GetForFeatures( + intent_segments, interest_segments, + [=](const bool was_allowed, + absl::optional ad) { + EXPECT_EQ(absl::nullopt, ad); + }); + + // Assert +} + +TEST_F(BatAdsEligibleAdNotificationsTest, GetForFeaturesWithEmptySegments) { + // Arrange + CreativeAdNotificationList creative_ad_notifications; + + CreativeAdNotificationInfo creative_ad_notification_1 = + GetCreativeAdNotificationForSegment("foo"); + creative_ad_notifications.push_back(creative_ad_notification_1); + + CreativeAdNotificationInfo creative_ad_notification_2 = + GetCreativeAdNotificationForSegment("foo-bar"); + creative_ad_notifications.push_back(creative_ad_notification_2); + + Save(creative_ad_notifications); + + const SegmentList intent_segments = {}; + const SegmentList interest_segments = {}; + + // Act + ad_targeting::geographic::SubdivisionTargeting subdivision_targeting; + resource::AntiTargeting anti_targeting_resource; + ad_notifications::EligibleAds eligible_ads(&subdivision_targeting, + &anti_targeting_resource); + + const CreativeAdNotificationInfo expected_ad = creative_ad_notification_2; + + eligible_ads.GetForFeatures( + intent_segments, interest_segments, + [=](const bool was_allowed, + absl::optional ad) { EXPECT_TRUE(ad); }); + + // Assert +} + +TEST_F(BatAdsEligibleAdNotificationsTest, GetForFeaturesDeterministically) { + // Arrange + CreativeAdNotificationList creative_ad_notifications; + + CreativeAdNotificationInfo creative_ad_notification_1 = + GetCreativeAdNotificationForSegment("travel"); + creative_ad_notifications.push_back(creative_ad_notification_1); + + Log(creative_ad_notification_1, /* last seen in hours */ 48); + + CreativeAdNotificationInfo creative_ad_notification_2 = + GetCreativeAdNotificationForSegment("travel-italy"); + creative_ad_notifications.push_back(creative_ad_notification_2); + + Log(creative_ad_notification_2, /* last seen in hours */ 16); + + Save(creative_ad_notifications); + + const SegmentList intent_segments = {"intent-foo", "intent-bar"}; + const SegmentList interest_segments = {"interest-foo", "interest-bar"}; + + const char kAdFeatureWeights[] = "ad_feature_weights"; + std::map kEligibleAdsParameters; + // Bias distribution to deterministically show an ad + kEligibleAdsParameters[kAdFeatureWeights] = + "0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0"; + + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitWithFeaturesAndParameters( + {{features::kEligibleAds, kEligibleAdsParameters}}, {}); + + // Act + ad_targeting::geographic::SubdivisionTargeting subdivision_targeting; + resource::AntiTargeting anti_targeting_resource; + ad_notifications::EligibleAds eligible_ads(&subdivision_targeting, + &anti_targeting_resource); + + const CreativeAdNotificationInfo expected_ad = creative_ad_notification_2; + + for (int i = 0; i < 10; i++) { + eligible_ads.GetForFeatures( + intent_segments, interest_segments, + [=](const bool was_allowed, + absl::optional ad) { + EXPECT_EQ(expected_ad.creative_instance_id, ad->creative_instance_id); + }); + } + // Assert +} + } // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features.cc b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features.cc new file mode 100644 index 000000000000..06388a2e755a --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features.cc @@ -0,0 +1,44 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/eligible_ads/eligible_ads_features.h" + +#include + +#include "base/metrics/field_trial_params.h" +#include "bat/ads/internal/eligible_ads/eligible_ads_util.h" + +namespace ads { +namespace features { + +namespace { + +const char kFeatureName[] = "EligibleAds"; +const char kFieldTrialParameterAdFeatureWeights[] = "ad_feature_weights"; +const std::vector kDefaultWeights = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; + +} // namespace + +const base::Feature kEligibleAds{kFeatureName, + base::FEATURE_ENABLED_BY_DEFAULT}; + +bool IsEligibleAdsEnabled() { + return base::FeatureList::IsEnabled(kEligibleAds); +} + +std::vector GetAdFeatureWeights() { + std::string param_value = GetFieldTrialParamValueByFeature( + kEligibleAds, kFieldTrialParameterAdFeatureWeights); + + std::vector weights = ToAdFeatureWeights(param_value); + if (weights.empty()) { + weights = kDefaultWeights; + } + + return weights; +} + +} // namespace features +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features.h b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features.h new file mode 100644 index 000000000000..1e4752c6a14d --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_VENDOR_BAT_NATIVE_ADS_SRC_BAT_ADS_INTERNAL_ELIGIBLE_ADS_ELIGIBLE_ADS_FEATURES_H_ +#define BRAVE_VENDOR_BAT_NATIVE_ADS_SRC_BAT_ADS_INTERNAL_ELIGIBLE_ADS_ELIGIBLE_ADS_FEATURES_H_ + +#include + +#include "base/feature_list.h" + +namespace ads { +namespace features { + +extern const base::Feature kEligibleAds; + +bool IsEligibleAdsEnabled(); + +std::vector GetAdFeatureWeights(); + +} // namespace features +} // namespace ads + +#endif // BRAVE_VENDOR_BAT_NATIVE_ADS_SRC_BAT_ADS_INTERNAL_ELIGIBLE_ADS_ELIGIBLE_ADS_FEATURES_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features_unittest.cc b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features_unittest.cc new file mode 100644 index 000000000000..298d102299ab --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_features_unittest.cc @@ -0,0 +1,53 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/eligible_ads/eligible_ads_features.h" + +#include + +#include "base/feature_list.h" +#include "base/test/scoped_feature_list.h" +#include "testing/gtest/include/gtest/gtest.h" + +// npm run test -- brave_unit_tests --filter=BatAds* + +namespace ads { + +namespace { +const unsigned int kNumberOfAdServingFeatures = 7u; +} // namespace + +TEST(BatAdsEligibleAdsFeaturesTest, EligibleAdsEnabled) { + // Arrange + + // Act + const bool is_enabled = features::IsEligibleAdsEnabled(); + + // Assert + EXPECT_TRUE(is_enabled); +} + +TEST(BatAdsEligibleAdsFeaturesTest, AdFeatureWeightLength) { + // Arrange + + // Act + std::vector weights = features::GetAdFeatureWeights(); + + // Assert + EXPECT_EQ(kNumberOfAdServingFeatures, weights.size()); +} + +TEST(BatAdsEligibleAdsFeaturesTest, DefaultAdFeatureWeights) { + // Arrange + + // Act + std::vector weights = features::GetAdFeatureWeights(); + + // Assert + std::vector expected_weights = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; + EXPECT_EQ(expected_weights, weights); +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util.cc b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util.cc new file mode 100644 index 000000000000..6319caee9916 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util.cc @@ -0,0 +1,104 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/eligible_ads/eligible_ads_util.h" + +#include +#include + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" + +namespace ads { + +// TODO(Moritz Haller): define alias for vector +std::vector ToAdFeatureWeights(const std::string& param_value) { + const std::vector components = base::SplitString( + param_value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + + std::vector weights; + for (const auto& component : components) { + double value_as_double; + if (!base::StringToDouble(component, &value_as_double)) { + return {}; + } + + if (value_as_double < 0) { + return {}; + } + + weights.push_back(value_as_double); + } + + double sum = std::accumulate(weights.begin(), weights.end(), + decltype(weights)::value_type(0)); + if (sum <= 0) { + return {}; + } + + return weights; +} + +bool DoesMatchSegments(const SegmentList& user_segments, + const SegmentList& ad_segments) { + std::vector v1 = user_segments; + std::vector v2 = ad_segments; + + std::sort(v1.begin(), v1.end()); + std::sort(v2.begin(), v2.end()); + + std::vector intersection; + std::set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), + std::back_inserter(intersection)); + + if (intersection.size() > 0) { + return true; + } + + return false; +} + +unsigned int CalculateAdLastSeenInHoursFeature( + const AdEventList& ad_events, + const std::string& creative_instance_id) { + // ad_events are ordered by timestamp desc in DB + const auto iter = std::find_if( + ad_events.begin(), ad_events.end(), + [&creative_instance_id](const AdEventInfo& ad_event) -> bool { + return (ad_event.creative_instance_id == creative_instance_id && + ad_event.confirmation_type == ConfirmationType::kViewed); + }); + + if (iter == ad_events.end()) { + return 0; + } + + const base::Time now = base::Time::Now(); + const base::Time timestamp = base::Time::FromDoubleT(iter->timestamp); + return (now - timestamp).InHours(); +} + +unsigned int CalculateAdvertiserLastSeenInHoursFeature( + const AdEventList& ad_events, + const std::string& advertiser_id) { + // ad_events are ordered by timestamp desc in DB + const auto iter = std::find_if( + ad_events.begin(), ad_events.end(), + [&advertiser_id](const AdEventInfo& ad_event) -> bool { + return (ad_event.advertiser_id == advertiser_id && + ad_event.confirmation_type == ConfirmationType::kViewed); + }); + + if (iter == ad_events.end()) { + return 0; + } + + const base::Time now = base::Time::Now(); + const base::Time timestamp = base::Time::FromDoubleT(iter->timestamp); + return (now - timestamp).InHours(); +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util.h b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util.h new file mode 100644 index 000000000000..ea5e3687c418 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util.h @@ -0,0 +1,168 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_VENDOR_BAT_NATIVE_ADS_SRC_BAT_ADS_INTERNAL_ELIGIBLE_ADS_ELIGIBLE_ADS_UTIL_H_ +#define BRAVE_VENDOR_BAT_NATIVE_ADS_SRC_BAT_ADS_INTERNAL_ELIGIBLE_ADS_ELIGIBLE_ADS_UTIL_H_ + +#include +#include +#include +#include + +#include "bat/ads/internal/eligible_ads/ad_features_info.h" +#include "bat/ads/internal/ad_targeting/ad_targeting_segment_util.h" +#include "bat/ads/internal/ad_events/ad_event_info.h" +#include "bat/ads/internal/eligible_ads/eligible_ads_features.h" +#include "base/rand_util.h" +#include "base/time/time.h" + +namespace ads { + +const int kMatchesIntentChildSegmentWeight = 0; // TODO(Moritz Haller): where to put magic constants? +const int kMatchesIntentParentSegmentWeight = 1; +const int kMatchesInterestChildSegmentWeight = 2; +const int kMatchesInterestParentSegmentWeight = 3; +const int kAdLastSeenInHoursWeight = 4; +const int kAdvertiserLastSeenInHoursWeight = 5; +const int kPriorityWeight = 6; + +std::vector ToAdFeatureWeights(const std::string& param_value); + +template +std::map> GroupEligibleAdsByCreativeInstanceId( + const std::vector& eligible_ads) { + std::map> ads; + for (const auto& eligible_ad : eligible_ads) { + const auto iter = ads.find(eligible_ad.creative_instance_id); + if (iter != ads.end()) { + iter->second.segments.push_back(eligible_ad.segment); + continue; + } + + AdFeaturesInfo ad_features; + ad_features.segments = {eligible_ad.segment}; + ad_features.creative_ad = eligible_ad; + ads.insert({eligible_ad.creative_instance_id, ad_features}); + } + + return ads; +} + +bool DoesMatchSegments(const SegmentList& user_segments, + const SegmentList& ad_segments); + +unsigned int CalculateAdLastSeenInHoursFeature( + const AdEventList& ad_events, + const std::string& creative_instance_id); + +unsigned int CalculateAdvertiserLastSeenInHoursFeature( + const AdEventList& ad_events, + const std::string& advertiser_id); + +template +double CalculateScore(const AdFeaturesInfo& ad_features) { + // TODO(Moritz Haller) Explain matching and weights + // currently seven features + // weights are non-negative reals, where sum_weights > 0 (see test) + std::vector weights = features::GetAdFeatureWeights(); + + double score = 0.0; + + if (ad_features.does_match_intent_child_segments) { + score += weights.at(kMatchesIntentChildSegmentWeight); + } else if (ad_features.does_match_intent_parent_segments) { + score += weights.at(kMatchesIntentParentSegmentWeight); + } + + if (ad_features.does_match_interest_child_segments) { + score += weights.at(kMatchesInterestChildSegmentWeight); + } else if (ad_features.does_match_interest_parent_segments) { + score += weights.at(kMatchesInterestParentSegmentWeight); + } + + if (ad_features.ad_last_seen_hours_ago <= base::Time::kHoursPerDay) { + score += weights.at(kAdLastSeenInHoursWeight) * + ad_features.ad_last_seen_hours_ago / base::Time::kHoursPerDay; + } + + if (ad_features.advertiser_last_seen_hours_ago <= base::Time::kHoursPerDay) { + score += weights.at(kAdvertiserLastSeenInHoursWeight) * + ad_features.advertiser_last_seen_hours_ago / + base::Time::kHoursPerDay; + } + + score += weights.at(kPriorityWeight) / + ad_features.creative_ad.priority; + + score *= ad_features.creative_ad.ptr; + + return score; +} + +template +std::map> ComputeFeaturesAndScores( + const std::map>& ads, + const AdEventList& ad_events, + const SegmentList& interest_segments, + const SegmentList& intent_segments) { + std::map> ads_with_features; + + for (auto& ad : ads) { + AdFeaturesInfo ad_features; + ad_features.does_match_intent_child_segments = + DoesMatchSegments(intent_segments, ad.second.segments); + ad_features.does_match_intent_parent_segments = DoesMatchSegments( + GetParentSegments(intent_segments), ad.second.segments); + ad_features.does_match_interest_child_segments = + DoesMatchSegments(interest_segments, ad.second.segments); + ad_features.does_match_interest_parent_segments = DoesMatchSegments( + GetParentSegments(interest_segments), ad.second.segments); + ad_features.ad_last_seen_hours_ago = CalculateAdLastSeenInHoursFeature( + ad_events, ad.second.creative_ad.creative_instance_id); + ad_features.advertiser_last_seen_hours_ago = + CalculateAdvertiserLastSeenInHoursFeature(ad_events, + ad.second.creative_ad.advertiser_id); + ad_features.score = CalculateScore(ad.second); + + ads_with_features.insert({ad.first, ad_features}); + } + + return ads_with_features; +} + +template +double CalculateNormalisingConstant( + const std::map>& ads) { + double normalising_constant = 0.0; + for (const auto& ad : ads) { + normalising_constant += ad.second.score; + } + + return normalising_constant; +} + +template +absl::optional SampleFromAds( + const std::map>& ads) { + double normalising_constant = CalculateNormalisingConstant(ads); + + const double rand = base::RandDouble(); + double sum = 0; + T selected_ad; + + for (const auto& ad : ads) { + double probability = ad.second.score / normalising_constant; + sum += probability; + if (rand < sum) { + return ad.second.creative_ad; + } + } + + return absl::nullopt; +} + +} // namespace ads + +#endif // BRAVE_VENDOR_BAT_NATIVE_ADS_SRC_BAT_ADS_INTERNAL_ELIGIBLE_ADS_ELIGIBLE_ADS_UTIL_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util_unittest.cc b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util_unittest.cc new file mode 100644 index 000000000000..2c51c52fcb0f --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/eligible_ads_util_unittest.cc @@ -0,0 +1,108 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/eligible_ads/eligible_ads_util.h" + +#include + +#include "bat/ads/internal/unittest_base.h" +#include "bat/ads/internal/unittest_util.h" +#include "bat/ads/internal/user_activity/user_activity_util.h" + +// npm run test -- brave_unit_tests --filter=BatAds* + +namespace ads { + +TEST(BatAdsEligibleAdsUtilTest, ToAdFeatureWeightsForEmptyParamValue) { + // Arrange + + // Act + const std::vector weights = ToAdFeatureWeights(""); + + // Assert + const std::vector expected_weights = {}; + EXPECT_EQ(expected_weights, weights); +} + +TEST(BatAdsEligibleAdsUtilTest, ToAdFeatureWeightsForNonNumericParamValue) { + // Arrange + + // Act + const std::vector weights = ToAdFeatureWeights("1.0, foobar, 2.2"); + + // Assert + const std::vector expected_weights = {}; + EXPECT_EQ(expected_weights, weights); +} + +TEST(BatAdsEligibleAdsUtilTest, ToAdFeatureWeightsForAllZeroParamValue) { + // Arrange + + // Act + const std::vector weights = ToAdFeatureWeights("0.0, 0.0, 0.0"); + + // Assert + const std::vector expected_weights = {}; + EXPECT_EQ(expected_weights, weights); +} + +TEST(BatAdsEligibleAdsUtilTest, ToAdFeatureWeightsForSomeZeroParamValue) { + // Arrange + + // Act + const std::vector weights = ToAdFeatureWeights("0.0, 0.1, 0.0"); + + // Assert + const std::vector expected_weights = {0.0, 0.1, 0.0}; + EXPECT_EQ(expected_weights, weights); +} + +TEST(BatAdsEligibleAdsUtilTest, ToAdFeatureWeightsForNegativeParamValue) { + // Arrange + + // Act + const std::vector weights = ToAdFeatureWeights("1.0, 3.0, -2.0"); + + // Assert + const std::vector expected_weights = {}; + EXPECT_EQ(expected_weights, weights); +} + +TEST(BatAdsEligibleAdsUtilTest, ToAdFeatureWeightsForSingleParamValue) { + // Arrange + + // Act + const std::vector weights = ToAdFeatureWeights("1.0"); + + // Assert + const std::vector expected_weights = {1.0}; + EXPECT_EQ(expected_weights, weights); +} + +TEST(BatAdsEligibleAdsUtilTest, ToAdFeatureWeightsForParamValue) { + // Arrange + + // Act + const std::vector weights = ToAdFeatureWeights("1.1, 3.3, 2.2"); + + // Assert + const std::vector expected_weights = {1.1, 3.3, 2.2}; + EXPECT_EQ(expected_weights, weights); +} + +TEST(BatAdsEligibleAdsUtilTest, ToAdFeatureWeightsForParamValueWithMixedTypes) { + // Arrange + + // Act + const std::vector weights = ToAdFeatureWeights("1, 3, 2.2"); + + // Assert + const std::vector expected_weights = {1.0, 3.0, 2.2}; + EXPECT_EQ(expected_weights, weights); +} + +// TODO(Moritz Haller): split out and test all utils + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads.cc b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads.cc index 66c11ac96660..0a74f59d1e41 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads.cc @@ -5,6 +5,7 @@ #include "bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads.h" +#include #include #include "bat/ads/inline_content_ad_info.h" @@ -18,6 +19,8 @@ #include "bat/ads/internal/client/client.h" #include "bat/ads/internal/database/tables/ad_events_database_table.h" #include "bat/ads/internal/database/tables/creative_inline_content_ads_database_table.h" +#include "bat/ads/internal/eligible_ads/ad_features_info.h" +#include "bat/ads/internal/eligible_ads/eligible_ads_util.h" #include "bat/ads/internal/eligible_ads/seen_ads.h" #include "bat/ads/internal/eligible_ads/seen_advertisers.h" #include "bat/ads/internal/features/ad_serving/ad_serving_features.h" @@ -76,8 +79,93 @@ void EligibleAds::GetForSegments(const SegmentList& segments, }); } +// TODO(Moritz Haller): create ticket "dev concerns" in new issues. Not for now: +// abstract out v1/v2 code (get for *) into concrete class +void EligibleAds::GetForFeatures(const SegmentList& interest_segments, + const SegmentList& intent_segments, + const std::string& dimensions, + SelectAdCallback callback) { // TODO(Moritz Haller): GetForFeaturesCallback + database::table::AdEvents database_table; + database_table.GetAll([=](const Result result, const AdEventList& ad_events) { + if (result != Result::SUCCESS) { + BLOG(1, "Failed to get ad events"); + callback(/* was_allowed */ false, absl::nullopt); + return; + } + + const int max_count = features::GetBrowsingHistoryMaxCount(); + const int days_ago = features::GetBrowsingHistoryDaysAgo(); + AdsClientHelper::Get()->GetBrowsingHistory( + max_count, days_ago, [=](const BrowsingHistoryList& history) { + GetEligibleAds(interest_segments, intent_segments, ad_events, history, + dimensions, callback); + }); + }); +} + + /////////////////////////////////////////////////////////////////////////////// +void EligibleAds::GetEligibleAds(const SegmentList& interest_segments, + const SegmentList& intent_segments, + const AdEventList& ad_events, + const BrowsingHistoryList& browsing_history, + const std::string& dimensions, + SelectAdCallback callback) const { + BLOG(1, "Get eligible ads"); + + database::table::CreativeInlineContentAds database_table; + database_table.GetForDimensions(dimensions, + [=](const Result result, + const CreativeInlineContentAdList& ads) { + if (ads.empty()) { + BLOG(1, "No ads"); + callback(/* was_allowed */ true, absl::nullopt); + return; + } + + CreativeInlineContentAdList eligible_ads = ApplyFrequencyCapping( + ads, + ShouldCapLastServedAd(ads) ? last_served_creative_ad_ + : CreativeAdInfo(), + ad_events, browsing_history); + + if (eligible_ads.empty()) { + BLOG(1, "No eligible ads"); + callback(/* was_allowed */ true, absl::nullopt); + return; + } + + ChooseAd(eligible_ads, ad_events, interest_segments, + intent_segments, callback); + }); +} + +void EligibleAds::ChooseAd( + const CreativeInlineContentAdList& eligible_ads, + const AdEventList& ad_events, + const SegmentList& interest_segments, + const SegmentList& intent_segments, + SelectAdCallback callback) const { + DCHECK(!eligible_ads.empty()); + + std::map> ads = + GroupEligibleAdsByCreativeInstanceId(eligible_ads); + + // TODO(Moritz Haller): pass by value good here, or by ref? + std::map> + ads_with_features = ComputeFeaturesAndScores(ads, ad_events, + interest_segments, intent_segments); + + // TODO(Moritz Haller): "slicing" won't work bc of here, as + // we would have to down-cast again to concrete sub-class + const absl::optional ad = + SampleFromAds(ads_with_features); + + callback(/* was_allowed */ true, ad); +} + + void EligibleAds::GetForParentChildSegments( const SegmentList& segments, const std::string& dimensions, diff --git a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads.h b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads.h index c9b4502baad0..24617625c1fe 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads.h +++ b/vendor/bat-native-ads/src/bat/ads/internal/eligible_ads/inline_content_ads/eligible_inline_content_ads.h @@ -12,9 +12,15 @@ #include "bat/ads/internal/ad_targeting/ad_targeting_segment.h" #include "bat/ads/internal/bundle/creative_inline_content_ad_info.h" #include "bat/ads/internal/frequency_capping/frequency_capping_aliases.h" +#include "third_party/abseil-cpp/absl/types/optional.h" namespace ads { +using SelectAdCallback = + std::function)>; + + namespace ad_targeting { namespace geographic { class SubdivisionTargeting; @@ -44,6 +50,11 @@ class EligibleAds { const std::string& dimensions, GetEligibleAdsCallback callback); + void GetForFeatures(const SegmentList& interest_segments, + const SegmentList& intent_segments, + const std::string& dimensions, + SelectAdCallback callback); + private: ad_targeting::geographic::SubdivisionTargeting* subdivision_targeting_; // NOT OWNED @@ -52,6 +63,20 @@ class EligibleAds { CreativeAdInfo last_served_creative_ad_; + void GetEligibleAds(const SegmentList& interest_segments, + const SegmentList& intent_segments, + const AdEventList& ad_events, + const BrowsingHistoryList& browsing_history, + const std::string& dimensions, + SelectAdCallback callback) const; + + void ChooseAd( + const CreativeInlineContentAdList& eligible_ads, + const AdEventList& ad_events, + const SegmentList& interest_segments, + const SegmentList& intent_segments, + SelectAdCallback callback) const; + void GetForParentChildSegments(const SegmentList& segments, const std::string& dimensions, const AdEventList& ad_events, diff --git a/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features.cc b/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features.cc index a349ad8a8a52..5913fdf7efda 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features.cc @@ -51,6 +51,9 @@ const char kFieldTrialParameterBrowsingHistoryDaysAgo[] = "browsing_history_days_ago"; const int kDefaultBrowsingHistoryDaysAgo = 180; +const char kFieldTrialParameterAdServingVersion[] = "ad_serving_version"; +const int kDefaultAdServingVersion = 1; + } // namespace const base::Feature kAdServing{kFeatureName, base::FEATURE_ENABLED_BY_DEFAULT}; @@ -119,5 +122,11 @@ int GetBrowsingHistoryDaysAgo() { kDefaultBrowsingHistoryDaysAgo); } +int GetAdServingVersion() { + return GetFieldTrialParamByFeatureAsInt(kAdServing, + kFieldTrialParameterAdServingVersion, + kDefaultAdServingVersion); +} + } // namespace features } // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features.h b/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features.h index 4b8623fd70b0..3ce6f786e605 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features.h +++ b/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features.h @@ -30,6 +30,8 @@ int GetMaximumPromotedContentAdsPerDay(); int GetBrowsingHistoryMaxCount(); int GetBrowsingHistoryDaysAgo(); +int GetAdServingVersion(); + } // namespace features } // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features_unittest.cc b/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features_unittest.cc index cad894835c78..fab414a23638 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features_unittest.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/features/ad_serving/ad_serving_features_unittest.cc @@ -564,4 +564,15 @@ TEST(BatAdsAdServingFeaturesTest, DisabledMaximumPromotedContentAdsPerDay) { maximum_promoted_content_ads_per_day); } +TEST(BatAdsAdServingFeaturesTest, DefaultAdServingVersion) { + // Arrange + + // Act + const int ad_serving_version = features::GetAdServingVersion(); + + // Assert + const int expected_ad_serving_version = 1; + EXPECT_EQ(expected_ad_serving_version, ad_serving_version); +} + } // namespace ads