mirror of
https://github.com/twitter/the-algorithm.git
synced 2024-11-13 23:25:10 +01:00
[docx] split commit for file 1600
Signed-off-by: Ari Archer <ari.web.xyz@gmail.com>
This commit is contained in:
parent
e27b2e31c3
commit
94812b2b2c
Binary file not shown.
@ -1,70 +0,0 @@
|
||||
package com.twitter.home_mixer.module
|
||||
|
||||
import com.google.inject.name.Named
|
||||
import com.google.inject.Provides
|
||||
import com.twitter.conversions.DurationOps.RichDuration
|
||||
import com.twitter.finagle.mtls.authentication.ServiceIdentifier
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.home_mixer.param.HomeMixerInjectionNames.TweetypieStaticEntitiesCache
|
||||
import com.twitter.inject.TwitterModule
|
||||
import com.twitter.product_mixer.shared_library.memcached_client.MemcachedClientBuilder
|
||||
import com.twitter.servo.cache.FinagleMemcache
|
||||
import com.twitter.servo.cache.KeyTransformer
|
||||
import com.twitter.servo.cache.KeyValueTransformingTtlCache
|
||||
import com.twitter.servo.cache.ObservableTtlCache
|
||||
import com.twitter.servo.cache.Serializer
|
||||
import com.twitter.servo.cache.ThriftSerializer
|
||||
import com.twitter.servo.cache.TtlCache
|
||||
import com.twitter.tweetypie.{thriftscala => tp}
|
||||
import javax.inject.Singleton
|
||||
import org.apache.thrift.protocol.TCompactProtocol
|
||||
|
||||
object TweetypieStaticEntitiesCacheClientModule extends TwitterModule {
|
||||
|
||||
private val ScopeName = "TweetypieStaticEntitiesMemcache"
|
||||
private val ProdDest = "/srv#/prod/local/cache/timelinescorer_tweet_core_data:twemcaches"
|
||||
|
||||
private val tweetsSerializer: Serializer[tp.Tweet] = {
|
||||
new ThriftSerializer[tp.Tweet](tp.Tweet, new TCompactProtocol.Factory())
|
||||
}
|
||||
private val keyTransformer: KeyTransformer[Long] = { tweetId => tweetId.toString }
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named(TweetypieStaticEntitiesCache)
|
||||
def providesTweetypieStaticEntitiesCache(
|
||||
statsReceiver: StatsReceiver,
|
||||
serviceIdentifier: ServiceIdentifier
|
||||
): TtlCache[Long, tp.Tweet] = {
|
||||
val memCacheClient = MemcachedClientBuilder.buildMemcachedClient(
|
||||
destName = ProdDest,
|
||||
numTries = 1,
|
||||
numConnections = 1,
|
||||
requestTimeout = 50.milliseconds,
|
||||
globalTimeout = 100.milliseconds,
|
||||
connectTimeout = 100.milliseconds,
|
||||
acquisitionTimeout = 100.milliseconds,
|
||||
serviceIdentifier = serviceIdentifier,
|
||||
statsReceiver = statsReceiver
|
||||
)
|
||||
mkCache(new FinagleMemcache(memCacheClient), statsReceiver)
|
||||
}
|
||||
|
||||
private def mkCache(
|
||||
finagleMemcache: FinagleMemcache,
|
||||
statsReceiver: StatsReceiver
|
||||
): TtlCache[Long, tp.Tweet] = {
|
||||
val baseCache: KeyValueTransformingTtlCache[Long, String, tp.Tweet, Array[Byte]] =
|
||||
new KeyValueTransformingTtlCache(
|
||||
underlyingCache = finagleMemcache,
|
||||
transformer = tweetsSerializer,
|
||||
underlyingKey = keyTransformer
|
||||
)
|
||||
ObservableTtlCache(
|
||||
underlyingCache = baseCache,
|
||||
statsReceiver = statsReceiver.scope(ScopeName),
|
||||
windowSize = 1000,
|
||||
name = ScopeName
|
||||
)
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/product",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,10 +0,0 @@
|
||||
package com.twitter.home_mixer.param
|
||||
|
||||
import com.twitter.inject.TwitterModule
|
||||
import com.twitter.product_mixer.core.functional_component.configapi.registry.GlobalParamConfig
|
||||
|
||||
object GlobalParamConfigModule extends TwitterModule {
|
||||
override def configure(): Unit = {
|
||||
bind[GlobalParamConfig].to[HomeGlobalParamConfig]
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,34 +0,0 @@
|
||||
package com.twitter.home_mixer.param
|
||||
|
||||
import com.twitter.home_mixer.param.HomeGlobalParams._
|
||||
import com.twitter.product_mixer.core.functional_component.configapi.registry.GlobalParamConfig
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Register Params that do not relate to a specific product. See GlobalParamConfig -> ParamConfig
|
||||
* for hooks to register Params based on type.
|
||||
*/
|
||||
@Singleton
|
||||
class HomeGlobalParamConfig @Inject() () extends GlobalParamConfig {
|
||||
|
||||
override val booleanFSOverrides = Seq(
|
||||
AdsDisableInjectionBasedOnUserRoleParam,
|
||||
EnableAdvertiserBrandSafetySettingsFeatureHydratorParam,
|
||||
EnableImpressionBloomFilter,
|
||||
EnableNahFeedbackInfoParam,
|
||||
EnableNewTweetsPillAvatarsParam,
|
||||
EnableScribeServedCandidatesParam,
|
||||
EnableSendScoresToClient,
|
||||
EnableSocialContextParam,
|
||||
)
|
||||
|
||||
override val boundedIntFSOverrides = Seq(
|
||||
MaxNumberReplaceInstructionsParam,
|
||||
TimelinesPersistenceStoreMaxEntriesPerClient,
|
||||
)
|
||||
|
||||
override val boundedDoubleFSOverrides = Seq(
|
||||
ImpressionBloomFilterFalsePositiveRateParam
|
||||
)
|
||||
}
|
Binary file not shown.
@ -1,86 +0,0 @@
|
||||
package com.twitter.home_mixer.param
|
||||
|
||||
import com.twitter.timelines.configapi.FSBoundedParam
|
||||
import com.twitter.timelines.configapi.FSParam
|
||||
|
||||
/**
|
||||
* Instantiate Params that do not relate to a specific product.
|
||||
*
|
||||
* @see [[com.twitter.product_mixer.core.product.ProductParamConfig.supportedClientFSName]]
|
||||
*/
|
||||
object HomeGlobalParams {
|
||||
|
||||
/**
|
||||
* This param is used to disable ads injection for timelines served by home-mixer.
|
||||
* It is currently used to maintain user-role based no-ads lists for automation accounts,
|
||||
* and should NOT be used for other purposes.
|
||||
*/
|
||||
object AdsDisableInjectionBasedOnUserRoleParam
|
||||
extends FSParam("home_mixer_ads_disable_injection_based_on_user_role", false)
|
||||
|
||||
object EnableSendScoresToClient
|
||||
extends FSParam[Boolean](
|
||||
name = "home_mixer_enable_send_scores_to_client",
|
||||
default = false
|
||||
)
|
||||
|
||||
object EnableNahFeedbackInfoParam
|
||||
extends FSParam[Boolean](
|
||||
name = "home_mixer_enable_nah_feedback_info",
|
||||
default = false
|
||||
)
|
||||
|
||||
object MaxNumberReplaceInstructionsParam
|
||||
extends FSBoundedParam[Int](
|
||||
name = "home_mixer_max_number_replace_instructions",
|
||||
default = 100,
|
||||
min = 0,
|
||||
max = 200
|
||||
)
|
||||
|
||||
object TimelinesPersistenceStoreMaxEntriesPerClient
|
||||
extends FSBoundedParam[Int](
|
||||
name = "home_mixer_timelines_persistence_store_max_entries_per_client",
|
||||
default = 1800,
|
||||
min = 500,
|
||||
max = 5000
|
||||
)
|
||||
|
||||
object EnableNewTweetsPillAvatarsParam
|
||||
extends FSParam[Boolean](
|
||||
name = "home_mixer_enable_new_tweets_pill_avatars",
|
||||
default = true
|
||||
)
|
||||
|
||||
object EnableSocialContextParam
|
||||
extends FSParam[Boolean](
|
||||
name = "home_mixer_enable_social_context",
|
||||
default = true
|
||||
)
|
||||
|
||||
object EnableAdvertiserBrandSafetySettingsFeatureHydratorParam
|
||||
extends FSParam[Boolean](
|
||||
name = "home_mixer_enable_advertiser_brand_safety_settings_feature_hydrator",
|
||||
default = true
|
||||
)
|
||||
|
||||
object EnableImpressionBloomFilter
|
||||
extends FSParam[Boolean](
|
||||
name = "home_mixer_enable_impression_bloom_filter",
|
||||
default = false
|
||||
)
|
||||
|
||||
object ImpressionBloomFilterFalsePositiveRateParam
|
||||
extends FSBoundedParam[Double](
|
||||
name = "home_mixer_impression_bloom_filter_false_positive_rate",
|
||||
default = 0.005,
|
||||
min = 0.001,
|
||||
max = 0.01
|
||||
)
|
||||
|
||||
object EnableScribeServedCandidatesParam
|
||||
extends FSParam[Boolean](
|
||||
name = "home_mixer_served_tweets_enable_scribing",
|
||||
default = true
|
||||
)
|
||||
}
|
Binary file not shown.
@ -1,13 +0,0 @@
|
||||
package com.twitter.home_mixer.param
|
||||
|
||||
object HomeMixerFlagName {
|
||||
final val ScribeClientEventsFlag = "scribe.client_events"
|
||||
final val ScribeServedCandidatesFlag = "scribe.served_candidates"
|
||||
final val ScribeScoredCandidatesFlag = "scribe.scored_candidates"
|
||||
final val ScribeServedCommonFeaturesAndCandidateFeaturesFlag =
|
||||
"scribe.served_common_features_and_candidate_features"
|
||||
final val DataRecordMetadataStoreConfigsYmlFlag = "data.record.metadata.store.configs.yml"
|
||||
final val DarkTrafficFilterDeciderKey = "thrift.dark.traffic.filter.decider_key"
|
||||
final val TargetFetchLatency = "target.fetch.latency"
|
||||
final val TargetScoringLatency = "target.scoring.latency"
|
||||
}
|
Binary file not shown.
@ -1,46 +0,0 @@
|
||||
package com.twitter.home_mixer.param
|
||||
|
||||
object HomeMixerInjectionNames {
|
||||
final val AuthorFeatureRepository = "AuthorFeatureRepository"
|
||||
final val CandidateFeaturesScribeEventPublisher = "CandidateFeaturesScribeEventPublisher"
|
||||
final val CommonFeaturesScribeEventPublisher = "CommonFeaturesScribeEventPublisher"
|
||||
final val EarlybirdRepository = "EarlybirdRepository"
|
||||
final val EngagementsReceivedByAuthorCache = "EngagementsReceivedByAuthorCache"
|
||||
final val GraphTwoHopRepository = "GraphTwoHopRepository"
|
||||
final val HomeAuthorFeaturesCacheClient = "HomeAuthorFeaturesCacheClient"
|
||||
final val InterestsThriftServiceClient = "InterestsThriftServiceClient"
|
||||
final val BatchedStratoClientWithModerateTimeout = "BatchedStratoClientWithModerateTimeout"
|
||||
final val ManhattanApolloClient = "ManhattanApolloClient"
|
||||
final val ManhattanAthenaClient = "ManhattanAthenaClient"
|
||||
final val ManhattanOmegaClient = "ManhattanOmegaClient"
|
||||
final val ManhattanStarbuckClient = "ManhattanStarbuckClient"
|
||||
final val MetricCenterUserCountingFeatureRepository = "MetricCenterUserCountingFeatureRepository"
|
||||
final val MinimumFeaturesScribeEventPublisher = "MinimumFeaturesScribeEventPublisher"
|
||||
final val RealGraphInNetworkScores = "RealGraphInNetworkScores"
|
||||
final val RealGraphManhattanEndpoint = "RealGraphFeaturesManhattanEndpoint"
|
||||
final val RealGraphFeatureRepository = "RealGraphFeatureRepository"
|
||||
final val RealTimeInteractionGraphUserVertexCache = "RealTimeInteractionGraphUserVertexCache"
|
||||
final val RealTimeInteractionGraphUserVertexClient = "RealTimeInteractionGraphUserVertexClient"
|
||||
final val StaleTweetsCache = "StaleTweetsCache"
|
||||
final val TimelineAggregateMetadataRepository = "TimelineAggregateMetadataRepository"
|
||||
final val TimelineAggregatePartARepository = "TimelineAggregatePartARepository"
|
||||
final val TimelineAggregatePartBRepository = "TimelineAggregatePartBRepository"
|
||||
final val TimelinesRealTimeAggregateClient = "TimelinesRealTimeAggregateClient"
|
||||
final val TopicCountryEngagementCache = "TopicCountryEngagementCache"
|
||||
final val TopicEngagementCache = "TopicEngagementCache"
|
||||
final val TweetCountryEngagementCache = "TweetCountryEngagementCache"
|
||||
final val TweetEngagementCache = "TweetEngagementCache"
|
||||
final val TweetypieContentRepository = "TweetypieContentRepository"
|
||||
final val TweetypieStaticEntitiesCache = "TweetypieStaticEntitiesCache"
|
||||
final val TwhinAuthorFollowFeatureCacheClient = "TwhinAuthorFollowFeatureCacheClient"
|
||||
final val TwhinAuthorFollowFeatureRepository = "TwhinAuthorFollowFeatureRepository"
|
||||
final val TwhinUserEngagementFeatureRepository = "TwhinUserEngagementFeatureRepository"
|
||||
final val TwhinUserFollowFeatureRepository = "TwhinUserFollowFeatureRepository"
|
||||
final val TwitterListEngagementCache = "TwitterListEngagementCache"
|
||||
final val UserAuthorEngagementCache = "UserAuthorEngagementCache"
|
||||
final val UserEngagementCache = "UserEngagementCache"
|
||||
final val UserFollowedTopicIdsRepository = "UserFollowedTopicIdsRepository"
|
||||
final val UserLanguagesRepository = "UserLanguagesRepository"
|
||||
final val UserTopicEngagementForNewUserCache = "UserTopicEngagementForNewUserCache"
|
||||
final val UtegSocialProofRepository = "UtegSocialProofRepository"
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"servo/decider",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,52 +0,0 @@
|
||||
package com.twitter.home_mixer.param.decider
|
||||
|
||||
import com.twitter.servo.decider.DeciderKeyEnum
|
||||
|
||||
/**
|
||||
* These values must correspond to the deciders configured in the
|
||||
* home-mixer/server/src/main/resources/config/decider.yml file
|
||||
*
|
||||
* @see [[com.twitter.product_mixer.core.product.ProductParamConfig.enabledDeciderKey]]
|
||||
*/
|
||||
object DeciderKey extends DeciderKeyEnum {
|
||||
// Products
|
||||
val EnableForYouProduct = Value("enable_for_you_product")
|
||||
|
||||
val EnableFollowingProduct = Value("enable_following_product")
|
||||
|
||||
val EnableScoredTweetsProduct = Value("enable_scored_tweets_product")
|
||||
|
||||
val EnableListTweetsProduct = Value("enable_list_tweets_product")
|
||||
|
||||
val EnableListRecommendedUsersProduct = Value("enable_list_recommended_users_product")
|
||||
|
||||
val EnableSubscribedProduct = Value("enable_subscribed_product")
|
||||
|
||||
// Candidate Pipelines
|
||||
val EnableForYouScoredTweetsCandidatePipeline =
|
||||
Value("enable_for_you_scored_tweets_candidate_pipeline")
|
||||
|
||||
val EnableScoredTweetsTweetMixerCandidatePipeline =
|
||||
Value("enable_scored_tweets_tweet_mixer_candidate_pipeline")
|
||||
|
||||
val EnableScoredTweetsInNetworkCandidatePipeline =
|
||||
Value("enable_scored_tweets_in_network_candidate_pipeline")
|
||||
|
||||
val EnableScoredTweetsUtegCandidatePipeline =
|
||||
Value("enable_scored_tweets_uteg_candidate_pipeline")
|
||||
|
||||
val EnableScoredTweetsFrsCandidatePipeline =
|
||||
Value("enable_scored_tweets_frs_candidate_pipeline")
|
||||
|
||||
val EnableScoredTweetsListsCandidatePipeline =
|
||||
Value("enable_scored_tweets_lists_candidate_pipeline")
|
||||
|
||||
val EnableScoredTweetsPopularVideosCandidatePipeline =
|
||||
Value("enable_scored_tweets_popular_videos_candidate_pipeline")
|
||||
|
||||
val EnableScoredTweetsBackfillCandidatePipeline =
|
||||
Value("enable_scored_tweets_backfill_candidate_pipeline")
|
||||
|
||||
val EnableSimClustersSimilarityFeatureHydration =
|
||||
Value("enable_simclusters_similarity_feature_hydration")
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
scala_library(
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"finatra/inject/inject-core/src/main/scala",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model/request",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/following",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/following/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/list_recommended_users",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/list_recommended_users/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/list_tweets",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/list_tweets/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/scored_tweets",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/scored_tweets/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/subscribed",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/subscribed/model",
|
||||
"home-mixer/thrift/src/main/thrift:thrift-scala",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/query/ads",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/product",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/product/guice",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/product/registry",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,11 +0,0 @@
|
||||
package com.twitter.home_mixer.product
|
||||
|
||||
import com.twitter.inject.TwitterModule
|
||||
import com.twitter.product_mixer.core.product.registry.ProductPipelineRegistryConfig
|
||||
|
||||
object HomeMixerProductModule extends TwitterModule {
|
||||
|
||||
override def configure(): Unit = {
|
||||
bind[ProductPipelineRegistryConfig].to[HomeProductPipelineRegistryConfig]
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,60 +0,0 @@
|
||||
package com.twitter.home_mixer.product
|
||||
|
||||
import com.twitter.home_mixer.model.request.FollowingProduct
|
||||
import com.twitter.home_mixer.model.request.ForYouProduct
|
||||
import com.twitter.home_mixer.model.request.ListRecommendedUsersProduct
|
||||
import com.twitter.home_mixer.model.request.ListTweetsProduct
|
||||
import com.twitter.home_mixer.model.request.ScoredTweetsProduct
|
||||
import com.twitter.home_mixer.model.request.SubscribedProduct
|
||||
import com.twitter.home_mixer.product.following.FollowingProductPipelineConfig
|
||||
import com.twitter.home_mixer.product.for_you.ForYouProductPipelineConfig
|
||||
import com.twitter.home_mixer.product.list_recommended_users.ListRecommendedUsersProductPipelineConfig
|
||||
import com.twitter.home_mixer.product.scored_tweets.ScoredTweetsProductPipelineConfig
|
||||
import com.twitter.home_mixer.product.list_tweets.ListTweetsProductPipelineConfig
|
||||
import com.twitter.home_mixer.product.subscribed.SubscribedProductPipelineConfig
|
||||
import com.twitter.inject.Injector
|
||||
import com.twitter.product_mixer.core.product.guice.ProductScope
|
||||
import com.twitter.product_mixer.core.product.registry.ProductPipelineRegistryConfig
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class HomeProductPipelineRegistryConfig @Inject() (
|
||||
injector: Injector,
|
||||
productScope: ProductScope)
|
||||
extends ProductPipelineRegistryConfig {
|
||||
|
||||
private val followingProductPipelineConfig = productScope.let(FollowingProduct) {
|
||||
injector.instance[FollowingProductPipelineConfig]
|
||||
}
|
||||
|
||||
private val forYouProductPipelineConfig = productScope.let(ForYouProduct) {
|
||||
injector.instance[ForYouProductPipelineConfig]
|
||||
}
|
||||
|
||||
private val scoredTweetsProductPipelineConfig = productScope.let(ScoredTweetsProduct) {
|
||||
injector.instance[ScoredTweetsProductPipelineConfig]
|
||||
}
|
||||
|
||||
private val listTweetsProductPipelineConfig = productScope.let(ListTweetsProduct) {
|
||||
injector.instance[ListTweetsProductPipelineConfig]
|
||||
}
|
||||
|
||||
private val listRecommendedUsersProductPipelineConfig =
|
||||
productScope.let(ListRecommendedUsersProduct) {
|
||||
injector.instance[ListRecommendedUsersProductPipelineConfig]
|
||||
}
|
||||
|
||||
private val subscribedProductPipelineConfig = productScope.let(SubscribedProduct) {
|
||||
injector.instance[SubscribedProductPipelineConfig]
|
||||
}
|
||||
|
||||
override val productPipelineConfigs = Seq(
|
||||
followingProductPipelineConfig,
|
||||
forYouProductPipelineConfig,
|
||||
scoredTweetsProductPipelineConfig,
|
||||
listTweetsProductPipelineConfig,
|
||||
listRecommendedUsersProductPipelineConfig,
|
||||
subscribedProductPipelineConfig,
|
||||
)
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"ads-injection/lib/src/main/scala/com/twitter/goldfinch/api",
|
||||
"finagle/finagle-memcached/src/main/scala",
|
||||
"finatra/inject/inject-core/src/main/scala",
|
||||
"finatra/inject/inject-core/src/main/scala/com/twitter/inject",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/candidate_pipeline",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/candidate_source",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/decorator",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/decorator/builder",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/decorator/urt/builder",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/feature_hydrator",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/filter",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/gate",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/selector",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/side_effect",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/marshaller/timelines",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model/request",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/param",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/following/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/following/param",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/service",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/util",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/candidate_source/account_recommendations_mixer",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/candidate_source/ads",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/candidate_source/hermit",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/candidate_source/tweetconvosvc",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/decorator/urt",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/candidate/ads",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/candidate/param_gated",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/query/async",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/query/impressed_tweets",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/query/param_gated",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/query/social_graph",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/filter",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/gate",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate/ads",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/presentation/urt",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/pipeline/candidate/ads",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/pipeline/candidate/flexible_injection_pipeline",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/pipeline/candidate/who_to_follow_module",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/premarshaller/urt",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/selector",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/selector/ads",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/side_effect",
|
||||
"product-mixer/core/src/main/java/com/twitter/product_mixer/core/product/guice/scope",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/configapi",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/decorator",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/decorator/urt/builder",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/marshaller/response/urt",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common/presentation",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/request",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/item",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline/candidate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline/mixer",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline/product",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/product",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/product/guice",
|
||||
"src/java/com/twitter/search/common/schema/base",
|
||||
"src/java/com/twitter/search/common/schema/earlybird",
|
||||
"src/java/com/twitter/search/common/util/lang",
|
||||
"src/java/com/twitter/search/queryparser/query:core-query-nodes",
|
||||
"src/java/com/twitter/search/queryparser/query/search:search-query-nodes",
|
||||
"src/scala/com/twitter/suggests/controller_data",
|
||||
"src/thrift/com/twitter/search:earlybird-scala",
|
||||
"src/thrift/com/twitter/search/common:constants-java",
|
||||
"src/thrift/com/twitter/suggests/controller_data:controller_data-scala",
|
||||
"src/thrift/com/twitter/timelinemixer:thrift-scala",
|
||||
"src/thrift/com/twitter/timelines/render:thrift-scala",
|
||||
"src/thrift/com/twitter/timelinescorer:thrift-scala",
|
||||
"src/thrift/com/twitter/timelinescorer/common/scoredtweetcandidate:thrift-scala",
|
||||
"src/thrift/com/twitter/timelinescorer/server/internal:thrift-scala",
|
||||
"src/thrift/com/twitter/tweetypie:service-scala",
|
||||
"stitch/stitch-gizmoduck",
|
||||
"stitch/stitch-tweetypie",
|
||||
"stringcenter/client",
|
||||
"stringcenter/client/src/main/java",
|
||||
"timelinemixer/server/src/main/scala/com/twitter/timelinemixer/injection/model/candidate",
|
||||
"timelines/src/main/scala/com/twitter/timelines/clients/relevance_search",
|
||||
"timelines/src/main/scala/com/twitter/timelines/injection/scribe",
|
||||
],
|
||||
exports = [
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/following/param",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/decorator/urt",
|
||||
"src/thrift/com/twitter/timelines/render:thrift-scala",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,108 +0,0 @@
|
||||
package com.twitter.home_mixer.product.following
|
||||
|
||||
import com.twitter.adserver.{thriftscala => ads}
|
||||
import com.twitter.home_mixer.functional_component.decorator.builder.HomeAdsClientEventDetailsBuilder
|
||||
import com.twitter.home_mixer.functional_component.gate.ExcludeSoftUserGate
|
||||
import com.twitter.home_mixer.model.HomeFeatures.TweetLanguageFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.TweetTextFeature
|
||||
import com.twitter.home_mixer.param.HomeGlobalParams
|
||||
import com.twitter.home_mixer.param.HomeGlobalParams.EnableAdvertiserBrandSafetySettingsFeatureHydratorParam
|
||||
import com.twitter.home_mixer.product.following.model.FollowingQuery
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam.EnableAdsCandidatePipelineParam
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam.EnableFastAds
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig
|
||||
import com.twitter.product_mixer.component_library.candidate_source.ads.AdsProdThriftCandidateSource
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.UrtItemCandidateDecorator
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.contextual_ref.ContextualTweetRefBuilder
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.ad.AdsCandidateUrtItemBuilder
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.metadata.ClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.candidate.ads.AdvertiserBrandSafetySettingsFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.candidate.param_gated.ParamGatedCandidateFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.gate.NonEmptyCandidatesGate
|
||||
import com.twitter.product_mixer.component_library.model.candidate.ads.AdsCandidate
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.AdsDependentCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.AdsDependentCandidatePipelineConfigBuilder
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.CountCandidatesFromPipelines
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.PipelineScopedOrganicItems
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.ValidAdImpressionIdFilter
|
||||
import com.twitter.product_mixer.core.functional_component.common.CandidateScope
|
||||
import com.twitter.product_mixer.core.gate.ParamNotGate
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidatePipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.rtf.safety_level.TimelineHomePromotedHydrationSafetyLevel
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.contextual_ref.TweetHydrationContext
|
||||
import com.twitter.timelines.injection.scribe.InjectionScribeUtil
|
||||
import com.twitter.timelineservice.suggests.{thriftscala => st}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FollowingAdsCandidatePipelineBuilder @Inject() (
|
||||
adsCandidatePipelineConfigBuilder: AdsDependentCandidatePipelineConfigBuilder,
|
||||
adsCandidateSource: AdsProdThriftCandidateSource,
|
||||
advertiserBrandSafetySettingsFeatureHydrator: AdvertiserBrandSafetySettingsFeatureHydrator[
|
||||
FollowingQuery,
|
||||
AdsCandidate
|
||||
]) {
|
||||
|
||||
private val identifier: CandidatePipelineIdentifier = CandidatePipelineIdentifier("FollowingAds")
|
||||
|
||||
private val suggestType = st.SuggestType.Promoted
|
||||
|
||||
private val clientEventInfoBuilder = ClientEventInfoBuilder(
|
||||
component = InjectionScribeUtil.scribeComponent(suggestType).get,
|
||||
detailsBuilder = Some(HomeAdsClientEventDetailsBuilder(Some(suggestType.name)))
|
||||
)
|
||||
|
||||
private val contextualTweetRefBuilder = ContextualTweetRefBuilder(
|
||||
TweetHydrationContext(
|
||||
safetyLevelOverride = Some(TimelineHomePromotedHydrationSafetyLevel),
|
||||
outerTweetContext = None
|
||||
))
|
||||
|
||||
private val decorator = UrtItemCandidateDecorator(
|
||||
AdsCandidateUrtItemBuilder(
|
||||
tweetClientEventInfoBuilder = Some(clientEventInfoBuilder),
|
||||
contextualTweetRefBuilder = Some(contextualTweetRefBuilder)
|
||||
))
|
||||
|
||||
private val alerts = Seq(
|
||||
HomeMixerAlertConfig.BusinessHours.defaultSuccessRateAlert(),
|
||||
HomeMixerAlertConfig.BusinessHours.defaultEmptyResponseRateAlert()
|
||||
)
|
||||
|
||||
def build(
|
||||
organicCandidatePipelines: CandidateScope
|
||||
): AdsDependentCandidatePipelineConfig[FollowingQuery] =
|
||||
adsCandidatePipelineConfigBuilder.build[FollowingQuery](
|
||||
adsCandidateSource = adsCandidateSource,
|
||||
identifier = identifier,
|
||||
adsDisplayLocationBuilder = query =>
|
||||
if (query.params.getBoolean(EnableFastAds)) ads.DisplayLocation.TimelineHomeReverseChron
|
||||
else ads.DisplayLocation.TimelineHome,
|
||||
getOrganicItems = PipelineScopedOrganicItems(
|
||||
pipelines = organicCandidatePipelines,
|
||||
textFeature = TweetTextFeature,
|
||||
languageFeature = TweetLanguageFeature
|
||||
),
|
||||
countNumOrganicItems = CountCandidatesFromPipelines(organicCandidatePipelines),
|
||||
supportedClientParam = Some(EnableAdsCandidatePipelineParam),
|
||||
gates = Seq(
|
||||
ParamNotGate(
|
||||
name = "AdsDisableInjectionBasedOnUserRole",
|
||||
param = HomeGlobalParams.AdsDisableInjectionBasedOnUserRoleParam
|
||||
),
|
||||
ExcludeSoftUserGate,
|
||||
NonEmptyCandidatesGate(organicCandidatePipelines)
|
||||
),
|
||||
filters = Seq(ValidAdImpressionIdFilter),
|
||||
postFilterFeatureHydration = Seq(
|
||||
ParamGatedCandidateFeatureHydrator(
|
||||
EnableAdvertiserBrandSafetySettingsFeatureHydratorParam,
|
||||
advertiserBrandSafetySettingsFeatureHydrator
|
||||
)
|
||||
),
|
||||
decorator = Some(decorator),
|
||||
alerts = alerts,
|
||||
urtRequest = Some(true),
|
||||
)
|
||||
}
|
Binary file not shown.
@ -1,54 +0,0 @@
|
||||
package com.twitter.home_mixer.product.following
|
||||
|
||||
import com.twitter.home_mixer.candidate_pipeline.FollowingEarlybirdResponseFeatureTransformer
|
||||
import com.twitter.home_mixer.functional_component.candidate_source.EarlybirdCandidateSource
|
||||
import com.twitter.home_mixer.product.following.model.FollowingQuery
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.social_graph.SGSFollowedUsersFeature
|
||||
import com.twitter.product_mixer.component_library.gate.NonEmptySeqFeatureGate
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.BaseCandidateSource
|
||||
import com.twitter.product_mixer.core.functional_component.gate.Gate
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidateFeatureTransformer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidatePipelineQueryTransformer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidatePipelineResultsTransformer
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidatePipelineIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.CandidatePipelineConfig
|
||||
import com.twitter.search.earlybird.{thriftscala => t}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FollowingEarlybirdCandidatePipelineConfig @Inject() (
|
||||
earlybirdCandidateSource: EarlybirdCandidateSource,
|
||||
followingEarlybirdQueryTransformer: FollowingEarlybirdQueryTransformer)
|
||||
extends CandidatePipelineConfig[
|
||||
FollowingQuery,
|
||||
t.EarlybirdRequest,
|
||||
t.ThriftSearchResult,
|
||||
TweetCandidate
|
||||
] {
|
||||
|
||||
override val identifier: CandidatePipelineIdentifier =
|
||||
CandidatePipelineIdentifier("FollowingEarlybird")
|
||||
|
||||
override val candidateSource: BaseCandidateSource[t.EarlybirdRequest, t.ThriftSearchResult] =
|
||||
earlybirdCandidateSource
|
||||
|
||||
override val gates: Seq[Gate[FollowingQuery]] = Seq(
|
||||
NonEmptySeqFeatureGate(SGSFollowedUsersFeature)
|
||||
)
|
||||
|
||||
override val queryTransformer: CandidatePipelineQueryTransformer[
|
||||
FollowingQuery,
|
||||
t.EarlybirdRequest
|
||||
] = followingEarlybirdQueryTransformer
|
||||
|
||||
override val featuresFromCandidateSourceTransformers: Seq[
|
||||
CandidateFeatureTransformer[t.ThriftSearchResult]
|
||||
] = Seq(FollowingEarlybirdResponseFeatureTransformer)
|
||||
|
||||
override val resultTransformer: CandidatePipelineResultsTransformer[
|
||||
t.ThriftSearchResult,
|
||||
TweetCandidate
|
||||
] = { sourceResult => TweetCandidate(id = sourceResult.id) }
|
||||
}
|
Binary file not shown.
@ -1,84 +0,0 @@
|
||||
package com.twitter.home_mixer.product.following
|
||||
|
||||
import com.twitter.finagle.thrift.ClientId
|
||||
import com.twitter.finagle.tracing.Trace
|
||||
import com.twitter.home_mixer.model.HomeFeatures.RealGraphInNetworkScoresFeature
|
||||
import com.twitter.home_mixer.product.following.model.FollowingQuery
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam.ServerMaxResultsParam
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.social_graph.SGSFollowedUsersFeature
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidatePipelineQueryTransformer
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.operation.BottomCursor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.operation.GapCursor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.operation.TopCursor
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.MalformedCursor
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.PipelineFailure
|
||||
import com.twitter.search.common.schema.earlybird.EarlybirdFieldConstants.EarlybirdFieldConstant
|
||||
import com.twitter.search.earlybird.{thriftscala => t}
|
||||
import com.twitter.search.queryparser.query.Conjunction
|
||||
import com.twitter.search.queryparser.query.search.SearchOperator
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import scala.jdk.CollectionConverters.asJavaIterableConverter
|
||||
|
||||
@Singleton
|
||||
case class FollowingEarlybirdQueryTransformer @Inject() (clientId: ClientId)
|
||||
extends CandidatePipelineQueryTransformer[FollowingQuery, t.EarlybirdRequest] {
|
||||
|
||||
override def transform(query: FollowingQuery): t.EarlybirdRequest = {
|
||||
val followedUserIds =
|
||||
query.features.map(_.get(SGSFollowedUsersFeature)).getOrElse(Seq.empty).toSet
|
||||
val realGraphInNetworkFollowedUserIds =
|
||||
query.features.map(_.get(RealGraphInNetworkScoresFeature)).getOrElse(Map.empty).keySet
|
||||
val userId = query.getRequiredUserId
|
||||
val combinedUserIds = userId +: followedUserIds.toSeq
|
||||
|
||||
val baseFollowedUsersSearchOperator = new SearchOperator.Builder()
|
||||
.setType(SearchOperator.Type.FEATURE_VALUE_IN_ACCEPT_LIST_OR_UNSET)
|
||||
.addOperand(EarlybirdFieldConstant.DIRECTED_AT_USER_ID_CSF.getFieldName)
|
||||
|
||||
val followedUsersQuery =
|
||||
baseFollowedUsersSearchOperator.addOperands(combinedUserIds.map(_.toString).asJava).build()
|
||||
|
||||
val searchQuery = query.pipelineCursor
|
||||
.map { cursor =>
|
||||
val sinceIdQuery =
|
||||
(id: Long) => new SearchOperator(SearchOperator.Type.SINCE_ID, id.toString)
|
||||
val maxIdQuery = // max ID is inclusive, so subtract 1
|
||||
(id: Long) => new SearchOperator(SearchOperator.Type.MAX_ID, (id - 1).toString)
|
||||
|
||||
(cursor.cursorType, cursor.id, cursor.gapBoundaryId) match {
|
||||
case (Some(TopCursor), Some(sinceId), _) =>
|
||||
new Conjunction(sinceIdQuery(sinceId), followedUsersQuery)
|
||||
case (Some(BottomCursor), Some(maxId), _) =>
|
||||
new Conjunction(maxIdQuery(maxId), followedUsersQuery)
|
||||
case (Some(GapCursor), Some(maxId), Some(sinceId)) =>
|
||||
new Conjunction(sinceIdQuery(sinceId), maxIdQuery(maxId), followedUsersQuery)
|
||||
case (Some(GapCursor), _, _) =>
|
||||
throw PipelineFailure(MalformedCursor, "Invalid cursor " + cursor.toString)
|
||||
case _ => followedUsersQuery
|
||||
}
|
||||
}.getOrElse(followedUsersQuery)
|
||||
|
||||
val metadataOptions = t.ThriftSearchResultMetadataOptions(
|
||||
getInReplyToStatusId = true,
|
||||
getReferencedTweetAuthorId = true,
|
||||
getFromUserId = true
|
||||
)
|
||||
|
||||
t.EarlybirdRequest(
|
||||
searchQuery = t.ThriftSearchQuery(
|
||||
serializedQuery = Some(searchQuery.serialize),
|
||||
fromUserIDFilter64 = Some(combinedUserIds),
|
||||
numResults = query.requestedMaxResults.getOrElse(query.params(ServerMaxResultsParam)),
|
||||
rankingMode = t.ThriftSearchRankingMode.Recency,
|
||||
resultMetadataOptions = Some(metadataOptions),
|
||||
searcherId = query.getOptionalUserId,
|
||||
),
|
||||
getOlderResults = Some(true), // needed for archive access to older tweets
|
||||
clientRequestID = Some(s"${Trace.id.traceId}"),
|
||||
followedUserIds = Some(combinedUserIds),
|
||||
numResultsToReturnAtRoot = Some(query.params(ServerMaxResultsParam)),
|
||||
clientId = Some(clientId.name),
|
||||
)
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,38 +0,0 @@
|
||||
package com.twitter.home_mixer.candidate_pipeline
|
||||
|
||||
import com.twitter.home_mixer.model.HomeFeatures.AuthorIdFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.InReplyToTweetIdFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.IsRetweetFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.SourceTweetIdFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.SourceUserIdFeature
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidateFeatureTransformer
|
||||
import com.twitter.product_mixer.core.model.common.identifier.TransformerIdentifier
|
||||
import com.twitter.search.earlybird.{thriftscala => t}
|
||||
|
||||
object FollowingEarlybirdResponseFeatureTransformer
|
||||
extends CandidateFeatureTransformer[t.ThriftSearchResult] {
|
||||
|
||||
override val identifier: TransformerIdentifier =
|
||||
TransformerIdentifier("FollowingEarlybirdResponse")
|
||||
|
||||
override val features: Set[Feature[_, _]] = Set(
|
||||
AuthorIdFeature,
|
||||
InReplyToTweetIdFeature,
|
||||
IsRetweetFeature,
|
||||
SourceTweetIdFeature,
|
||||
SourceUserIdFeature,
|
||||
)
|
||||
|
||||
override def transform(candidate: t.ThriftSearchResult): FeatureMap = FeatureMapBuilder()
|
||||
.add(AuthorIdFeature, candidate.tweetypieTweet.flatMap(_.coreData.map(_.userId)))
|
||||
.add(
|
||||
InReplyToTweetIdFeature,
|
||||
candidate.tweetypieTweet.flatMap(_.coreData.flatMap(_.reply.flatMap(_.inReplyToStatusId))))
|
||||
.add(IsRetweetFeature, candidate.metadata.exists(_.isRetweet.contains(true)))
|
||||
.add(SourceTweetIdFeature, candidate.sourceTweetypieTweet.map(_.id))
|
||||
.add(SourceUserIdFeature, candidate.sourceTweetypieTweet.flatMap(_.coreData.map(_.userId)))
|
||||
.build()
|
||||
}
|
Binary file not shown.
@ -1,299 +0,0 @@
|
||||
package com.twitter.home_mixer.product.following
|
||||
|
||||
import com.twitter.clientapp.{thriftscala => ca}
|
||||
import com.twitter.goldfinch.api.AdsInjectionSurfaceAreas
|
||||
import com.twitter.home_mixer.candidate_pipeline.ConversationServiceCandidatePipelineConfigBuilder
|
||||
import com.twitter.home_mixer.candidate_pipeline.EditedTweetsCandidatePipelineConfig
|
||||
import com.twitter.home_mixer.candidate_pipeline.NewTweetsPillCandidatePipelineConfig
|
||||
import com.twitter.home_mixer.functional_component.decorator.HomeConversationServiceCandidateDecorator
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.AddEntriesWithReplaceAndShowAlertAndCoverInstructionBuilder
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.HomeFeedbackActionInfoBuilder
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator._
|
||||
import com.twitter.home_mixer.functional_component.selector.UpdateHomeClientEventDetails
|
||||
import com.twitter.home_mixer.functional_component.selector.UpdateNewTweetsPillDecoration
|
||||
import com.twitter.home_mixer.functional_component.side_effect._
|
||||
import com.twitter.home_mixer.model.GapIncludeInstruction
|
||||
import com.twitter.home_mixer.param.HomeGlobalParams.EnableImpressionBloomFilter
|
||||
import com.twitter.home_mixer.param.HomeGlobalParams.MaxNumberReplaceInstructionsParam
|
||||
import com.twitter.home_mixer.param.HomeMixerFlagName.ScribeClientEventsFlag
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.social_graph.SGSFollowedUsersQueryFeatureHydrator
|
||||
import com.twitter.home_mixer.product.following.model.FollowingQuery
|
||||
import com.twitter.home_mixer.product.following.model.HomeMixerExternalStrings
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam.EnableFlipInjectionModuleCandidatePipelineParam
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam.FlipInlineInjectionModulePosition
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam.ServerMaxResultsParam
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam.WhoToFollowPositionParam
|
||||
import com.twitter.home_mixer.util.CandidatesUtil
|
||||
import com.twitter.inject.annotations.Flag
|
||||
import com.twitter.logpipeline.client.common.EventPublisher
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.async.AsyncQueryFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.impressed_tweets.ImpressedTweetsQueryFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.param_gated.AsyncParamGatedQueryFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.gate.NonEmptyCandidatesGate
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.flexible_injection_pipeline.FlipPromptDependentCandidatePipelineConfigBuilder
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.who_to_follow_module.WhoToFollowArmCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.UrtDomainMarshaller
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.OrderedBottomCursorBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.OrderedGapCursorBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.OrderedTopCursorBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ReplaceAllEntries
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ReplaceEntryInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ShowAlertInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ShowCoverInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.StaticTimelineScribeConfigBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.UrtMetadataBuilder
|
||||
import com.twitter.product_mixer.component_library.selector.DropMaxCandidates
|
||||
import com.twitter.product_mixer.component_library.selector.DropMaxModuleItemCandidates
|
||||
import com.twitter.product_mixer.component_library.selector.DropModuleTooFewModuleItemResults
|
||||
import com.twitter.product_mixer.component_library.selector.InsertAppendResults
|
||||
import com.twitter.product_mixer.component_library.selector.InsertFixedPositionResults
|
||||
import com.twitter.product_mixer.component_library.selector.SelectConditionally
|
||||
import com.twitter.product_mixer.component_library.selector.UpdateSortCandidates
|
||||
import com.twitter.product_mixer.component_library.selector.ads.AdsInjector
|
||||
import com.twitter.product_mixer.component_library.selector.ads.InsertAdResults
|
||||
import com.twitter.product_mixer.core.functional_component.common.SpecificPipeline
|
||||
import com.twitter.product_mixer.core.functional_component.common.SpecificPipelines
|
||||
import com.twitter.product_mixer.core.functional_component.configapi.StaticParam
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.functional_component.marshaller.TransportMarshaller
|
||||
import com.twitter.product_mixer.core.functional_component.marshaller.response.urt.UrtTransportMarshaller
|
||||
import com.twitter.product_mixer.core.functional_component.premarshaller.DomainMarshaller
|
||||
import com.twitter.product_mixer.core.functional_component.selector.Selector
|
||||
import com.twitter.product_mixer.core.functional_component.side_effect.PipelineResultSideEffect
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidatePipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.MixerPipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.Timeline
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineModule
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineScribeConfig
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.tweet.TweetItem
|
||||
import com.twitter.product_mixer.core.pipeline.FailOpenPolicy
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.CandidatePipelineConfig
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.DependentCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.core.pipeline.mixer.MixerPipelineConfig
|
||||
import com.twitter.product_mixer.core.product.guice.scope.ProductScoped
|
||||
import com.twitter.stringcenter.client.StringCenter
|
||||
import com.twitter.timelines.render.{thriftscala => urt}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FollowingMixerPipelineConfig @Inject() (
|
||||
followingEarlybirdCandidatePipelineConfig: FollowingEarlybirdCandidatePipelineConfig,
|
||||
conversationServiceCandidatePipelineConfigBuilder: ConversationServiceCandidatePipelineConfigBuilder[
|
||||
FollowingQuery
|
||||
],
|
||||
homeFeedbackActionInfoBuilder: HomeFeedbackActionInfoBuilder,
|
||||
followingAdsCandidatePipelineBuilder: FollowingAdsCandidatePipelineBuilder,
|
||||
followingWhoToFollowCandidatePipelineConfigBuilder: FollowingWhoToFollowCandidatePipelineConfigBuilder,
|
||||
flipPromptDependentCandidatePipelineConfigBuilder: FlipPromptDependentCandidatePipelineConfigBuilder,
|
||||
editedTweetsCandidatePipelineConfig: EditedTweetsCandidatePipelineConfig,
|
||||
newTweetsPillCandidatePipelineConfig: NewTweetsPillCandidatePipelineConfig[FollowingQuery],
|
||||
dismissInfoQueryFeatureHydrator: DismissInfoQueryFeatureHydrator,
|
||||
gizmoduckUserQueryFeatureHydrator: GizmoduckUserQueryFeatureHydrator,
|
||||
persistenceStoreQueryFeatureHydrator: PersistenceStoreQueryFeatureHydrator,
|
||||
realGraphInNetworkSourceQueryHydrator: RealGraphInNetworkScoresQueryFeatureHydrator,
|
||||
requestQueryFeatureHydrator: RequestQueryFeatureHydrator[FollowingQuery],
|
||||
sgsFollowedUsersQueryFeatureHydrator: SGSFollowedUsersQueryFeatureHydrator,
|
||||
impressionBloomFilterQueryFeatureHydrator: ImpressionBloomFilterQueryFeatureHydrator[
|
||||
FollowingQuery
|
||||
],
|
||||
manhattanTweetImpressionsQueryFeatureHydrator: TweetImpressionsQueryFeatureHydrator[
|
||||
FollowingQuery
|
||||
],
|
||||
memcacheTweetImpressionsQueryFeatureHydrator: ImpressedTweetsQueryFeatureHydrator,
|
||||
lastNonPollingTimeQueryFeatureHydrator: LastNonPollingTimeQueryFeatureHydrator,
|
||||
adsInjector: AdsInjector,
|
||||
updateLastNonPollingTimeSideEffect: UpdateLastNonPollingTimeSideEffect[FollowingQuery, Timeline],
|
||||
publishClientSentImpressionsEventBusSideEffect: PublishClientSentImpressionsEventBusSideEffect,
|
||||
publishClientSentImpressionsManhattanSideEffect: PublishClientSentImpressionsManhattanSideEffect,
|
||||
publishImpressionBloomFilterSideEffect: PublishImpressionBloomFilterSideEffect,
|
||||
updateTimelinesPersistenceStoreSideEffect: UpdateTimelinesPersistenceStoreSideEffect,
|
||||
truncateTimelinesPersistenceStoreSideEffect: TruncateTimelinesPersistenceStoreSideEffect,
|
||||
homeTimelineServedCandidatesSideEffect: HomeScribeServedCandidatesSideEffect,
|
||||
clientEventsScribeEventPublisher: EventPublisher[ca.LogEvent],
|
||||
externalStrings: HomeMixerExternalStrings,
|
||||
@ProductScoped stringCenterProvider: Provider[StringCenter],
|
||||
urtTransportMarshaller: UrtTransportMarshaller,
|
||||
@Flag(ScribeClientEventsFlag) enableScribeClientEvents: Boolean)
|
||||
extends MixerPipelineConfig[FollowingQuery, Timeline, urt.TimelineResponse] {
|
||||
|
||||
override val identifier: MixerPipelineIdentifier = MixerPipelineIdentifier("Following")
|
||||
|
||||
private val dependentCandidatesStep = MixerPipelineConfig.dependentCandidatePipelinesStep
|
||||
private val resultSelectorsStep = MixerPipelineConfig.resultSelectorsStep
|
||||
|
||||
override val fetchQueryFeatures: Seq[QueryFeatureHydrator[FollowingQuery]] = Seq(
|
||||
requestQueryFeatureHydrator,
|
||||
sgsFollowedUsersQueryFeatureHydrator,
|
||||
realGraphInNetworkSourceQueryHydrator,
|
||||
AsyncQueryFeatureHydrator(dependentCandidatesStep, dismissInfoQueryFeatureHydrator),
|
||||
AsyncQueryFeatureHydrator(dependentCandidatesStep, gizmoduckUserQueryFeatureHydrator),
|
||||
AsyncQueryFeatureHydrator(dependentCandidatesStep, persistenceStoreQueryFeatureHydrator),
|
||||
AsyncQueryFeatureHydrator(dependentCandidatesStep, lastNonPollingTimeQueryFeatureHydrator),
|
||||
AsyncParamGatedQueryFeatureHydrator(
|
||||
EnableImpressionBloomFilter,
|
||||
resultSelectorsStep,
|
||||
impressionBloomFilterQueryFeatureHydrator),
|
||||
AsyncQueryFeatureHydrator(resultSelectorsStep, manhattanTweetImpressionsQueryFeatureHydrator),
|
||||
AsyncQueryFeatureHydrator(resultSelectorsStep, memcacheTweetImpressionsQueryFeatureHydrator)
|
||||
)
|
||||
|
||||
private val earlybirdCandidatePipelineScope =
|
||||
SpecificPipeline(followingEarlybirdCandidatePipelineConfig.identifier)
|
||||
|
||||
private val conversationServiceCandidatePipelineConfig =
|
||||
conversationServiceCandidatePipelineConfigBuilder.build(
|
||||
Seq(NonEmptyCandidatesGate(earlybirdCandidatePipelineScope)),
|
||||
HomeConversationServiceCandidateDecorator(homeFeedbackActionInfoBuilder)
|
||||
)
|
||||
|
||||
private val followingAdsCandidatePipelineConfig =
|
||||
followingAdsCandidatePipelineBuilder.build(earlybirdCandidatePipelineScope)
|
||||
|
||||
private val followingWhoToFollowCandidatePipelineConfig =
|
||||
followingWhoToFollowCandidatePipelineConfigBuilder.build(earlybirdCandidatePipelineScope)
|
||||
|
||||
private val flipPromptCandidatePipelineConfig =
|
||||
flipPromptDependentCandidatePipelineConfigBuilder.build[FollowingQuery](
|
||||
supportedClientParam = Some(EnableFlipInjectionModuleCandidatePipelineParam)
|
||||
)
|
||||
|
||||
override val candidatePipelines: Seq[CandidatePipelineConfig[FollowingQuery, _, _, _]] =
|
||||
Seq(followingEarlybirdCandidatePipelineConfig)
|
||||
|
||||
override val dependentCandidatePipelines: Seq[
|
||||
DependentCandidatePipelineConfig[FollowingQuery, _, _, _]
|
||||
] = Seq(
|
||||
conversationServiceCandidatePipelineConfig,
|
||||
followingAdsCandidatePipelineConfig,
|
||||
followingWhoToFollowCandidatePipelineConfig,
|
||||
flipPromptCandidatePipelineConfig,
|
||||
editedTweetsCandidatePipelineConfig,
|
||||
newTweetsPillCandidatePipelineConfig
|
||||
)
|
||||
|
||||
override val failOpenPolicies: Map[CandidatePipelineIdentifier, FailOpenPolicy] = Map(
|
||||
followingAdsCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
followingWhoToFollowCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
flipPromptCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
editedTweetsCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
newTweetsPillCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
)
|
||||
|
||||
override val resultSelectors: Seq[Selector[FollowingQuery]] = Seq(
|
||||
UpdateSortCandidates(
|
||||
ordering = CandidatesUtil.reverseChronTweetsOrdering,
|
||||
candidatePipeline = conversationServiceCandidatePipelineConfig.identifier
|
||||
),
|
||||
DropMaxCandidates(
|
||||
candidatePipeline = editedTweetsCandidatePipelineConfig.identifier,
|
||||
maxSelectionsParam = MaxNumberReplaceInstructionsParam
|
||||
),
|
||||
DropMaxCandidates(
|
||||
candidatePipeline = conversationServiceCandidatePipelineConfig.identifier,
|
||||
maxSelectionsParam = ServerMaxResultsParam
|
||||
),
|
||||
DropModuleTooFewModuleItemResults(
|
||||
candidatePipeline = followingWhoToFollowCandidatePipelineConfig.identifier,
|
||||
minModuleItemsParam = StaticParam(WhoToFollowArmCandidatePipelineConfig.MinCandidatesSize)
|
||||
),
|
||||
DropMaxModuleItemCandidates(
|
||||
candidatePipeline = followingWhoToFollowCandidatePipelineConfig.identifier,
|
||||
maxModuleItemsParam = StaticParam(WhoToFollowArmCandidatePipelineConfig.MaxCandidatesSize)
|
||||
),
|
||||
InsertAppendResults(candidatePipeline = conversationServiceCandidatePipelineConfig.identifier),
|
||||
InsertFixedPositionResults(
|
||||
candidatePipeline = followingWhoToFollowCandidatePipelineConfig.identifier,
|
||||
positionParam = WhoToFollowPositionParam
|
||||
),
|
||||
InsertFixedPositionResults(
|
||||
candidatePipeline = flipPromptCandidatePipelineConfig.identifier,
|
||||
positionParam = FlipInlineInjectionModulePosition
|
||||
),
|
||||
InsertAdResults(
|
||||
surfaceAreaName = AdsInjectionSurfaceAreas.HomeTimeline,
|
||||
adsInjector = adsInjector.forSurfaceArea(AdsInjectionSurfaceAreas.HomeTimeline),
|
||||
adsCandidatePipeline = followingAdsCandidatePipelineConfig.identifier
|
||||
),
|
||||
// This selector must come after the tweets are inserted into the results
|
||||
UpdateNewTweetsPillDecoration(
|
||||
pipelineScope = SpecificPipelines(
|
||||
conversationServiceCandidatePipelineConfig.identifier,
|
||||
newTweetsPillCandidatePipelineConfig.identifier
|
||||
),
|
||||
stringCenter = stringCenterProvider.get(),
|
||||
seeNewTweetsString = externalStrings.seeNewTweetsString,
|
||||
tweetedString = externalStrings.tweetedString
|
||||
),
|
||||
InsertAppendResults(candidatePipeline = editedTweetsCandidatePipelineConfig.identifier),
|
||||
SelectConditionally(
|
||||
selector =
|
||||
InsertAppendResults(candidatePipeline = newTweetsPillCandidatePipelineConfig.identifier),
|
||||
includeSelector = (_, _, results) => CandidatesUtil.containsType[TweetCandidate](results)
|
||||
),
|
||||
UpdateHomeClientEventDetails(
|
||||
candidatePipelines = Set(conversationServiceCandidatePipelineConfig.identifier)
|
||||
),
|
||||
)
|
||||
|
||||
private val homeScribeClientEventSideEffect = HomeScribeClientEventSideEffect(
|
||||
enableScribeClientEvents = enableScribeClientEvents,
|
||||
logPipelinePublisher = clientEventsScribeEventPublisher,
|
||||
injectedTweetsCandidatePipelineIdentifiers =
|
||||
Seq(conversationServiceCandidatePipelineConfig.identifier),
|
||||
adsCandidatePipelineIdentifier = Some(followingAdsCandidatePipelineConfig.identifier),
|
||||
whoToFollowCandidatePipelineIdentifier =
|
||||
Some(followingWhoToFollowCandidatePipelineConfig.identifier),
|
||||
)
|
||||
|
||||
override val resultSideEffects: Seq[PipelineResultSideEffect[FollowingQuery, Timeline]] = Seq(
|
||||
homeScribeClientEventSideEffect,
|
||||
homeTimelineServedCandidatesSideEffect,
|
||||
publishClientSentImpressionsEventBusSideEffect,
|
||||
publishClientSentImpressionsManhattanSideEffect,
|
||||
publishImpressionBloomFilterSideEffect,
|
||||
truncateTimelinesPersistenceStoreSideEffect,
|
||||
updateLastNonPollingTimeSideEffect,
|
||||
updateTimelinesPersistenceStoreSideEffect,
|
||||
)
|
||||
|
||||
override val domainMarshaller: DomainMarshaller[FollowingQuery, Timeline] = {
|
||||
val instructionBuilders = Seq(
|
||||
ReplaceEntryInstructionBuilder(ReplaceAllEntries),
|
||||
AddEntriesWithReplaceAndShowAlertAndCoverInstructionBuilder(),
|
||||
ShowAlertInstructionBuilder(),
|
||||
ShowCoverInstructionBuilder(),
|
||||
)
|
||||
|
||||
val idSelector: PartialFunction[UniversalNoun[_], Long] = {
|
||||
// exclude ads while determining tweet cursor values
|
||||
case item: TweetItem if item.promotedMetadata.isEmpty => item.id
|
||||
case module: TimelineModule
|
||||
if module.items.headOption.exists(_.item.isInstanceOf[TweetItem]) =>
|
||||
module.items.last.item match { case item: TweetItem => item.id }
|
||||
}
|
||||
val topCursorBuilder = OrderedTopCursorBuilder(idSelector)
|
||||
val bottomCursorBuilder =
|
||||
OrderedBottomCursorBuilder(idSelector, GapIncludeInstruction.inverse())
|
||||
val gapCursorBuilder = OrderedGapCursorBuilder(idSelector, GapIncludeInstruction)
|
||||
|
||||
val metadataBuilder = UrtMetadataBuilder(
|
||||
title = None,
|
||||
scribeConfigBuilder = Some(
|
||||
StaticTimelineScribeConfigBuilder(
|
||||
TimelineScribeConfig(page = Some("following"), section = None, entityToken = None)))
|
||||
)
|
||||
|
||||
UrtDomainMarshaller(
|
||||
instructionBuilders = instructionBuilders,
|
||||
metadataBuilder = Some(metadataBuilder),
|
||||
cursorBuilders = Seq(topCursorBuilder, bottomCursorBuilder, gapCursorBuilder)
|
||||
)
|
||||
}
|
||||
|
||||
override val transportMarshaller: TransportMarshaller[Timeline, urt.TimelineResponse] =
|
||||
urtTransportMarshaller
|
||||
}
|
Binary file not shown.
@ -1,131 +0,0 @@
|
||||
package com.twitter.home_mixer.product.following
|
||||
|
||||
import com.twitter.conversions.DurationOps._
|
||||
import com.twitter.home_mixer.marshaller.timelines.ChronologicalCursorUnmarshaller
|
||||
import com.twitter.home_mixer.model.request.FollowingProduct
|
||||
import com.twitter.home_mixer.model.request.FollowingProductContext
|
||||
import com.twitter.home_mixer.model.request.HomeMixerRequest
|
||||
import com.twitter.home_mixer.product.following.model.FollowingQuery
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam.ServerMaxResultsParam
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParamConfig
|
||||
import com.twitter.home_mixer.service.HomeMixerAccessPolicy.DefaultHomeMixerAccessPolicy
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig.DefaultNotificationGroup
|
||||
import com.twitter.product_mixer.component_library.model.cursor.UrtOrderedCursor
|
||||
import com.twitter.product_mixer.component_library.premarshaller.cursor.UrtCursorSerializer
|
||||
import com.twitter.product_mixer.core.functional_component.common.access_policy.AccessPolicy
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.EmptyResponseRateAlert
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.LatencyAlert
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.P99
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.SuccessRateAlert
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.ThroughputAlert
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.predicate.TriggerIfAbove
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.predicate.TriggerIfBelow
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.predicate.TriggerIfLatencyAbove
|
||||
import com.twitter.product_mixer.core.model.common.identifier.ComponentIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.ProductPipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.request.Product
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.operation.GapCursor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.operation.TopCursor
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineConfig
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.BadRequest
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.MalformedCursor
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.PipelineFailure
|
||||
import com.twitter.product_mixer.core.pipeline.product.ProductPipelineConfig
|
||||
import com.twitter.product_mixer.core.product.ProductParamConfig
|
||||
import com.twitter.product_mixer.core.util.SortIndexBuilder
|
||||
import com.twitter.timelines.configapi.Params
|
||||
import com.twitter.timelines.render.{thriftscala => urt}
|
||||
import com.twitter.timelines.util.RequestCursorSerializer
|
||||
import com.twitter.util.Time
|
||||
import com.twitter.util.Try
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FollowingProductPipelineConfig @Inject() (
|
||||
followingMixerPipelineConfig: FollowingMixerPipelineConfig,
|
||||
followingParamConfig: FollowingParamConfig)
|
||||
extends ProductPipelineConfig[HomeMixerRequest, FollowingQuery, urt.TimelineResponse] {
|
||||
|
||||
override val identifier: ProductPipelineIdentifier = ProductPipelineIdentifier("Following")
|
||||
|
||||
override val product: Product = FollowingProduct
|
||||
override val paramConfig: ProductParamConfig = followingParamConfig
|
||||
|
||||
override def pipelineQueryTransformer(
|
||||
request: HomeMixerRequest,
|
||||
params: Params
|
||||
): FollowingQuery = {
|
||||
val context = request.productContext match {
|
||||
case Some(context: FollowingProductContext) => context
|
||||
case _ => throw PipelineFailure(BadRequest, "FollowingProductContext not found")
|
||||
}
|
||||
|
||||
val debugOptions = request.debugParams.flatMap(_.debugOptions)
|
||||
|
||||
/**
|
||||
* Unlike other clients, newly created tweets on Android have the sort index set to the current
|
||||
* time instead of the top sort index + 1, so these tweets get stuck at the top of the timeline
|
||||
* if subsequent timeline responses use the sort index from the previous response instead of
|
||||
* the current time.
|
||||
*/
|
||||
val pipelineCursor = request.serializedRequestCursor.flatMap { cursor =>
|
||||
Try(UrtCursorSerializer.deserializeOrderedCursor(cursor))
|
||||
.getOrElse(ChronologicalCursorUnmarshaller(RequestCursorSerializer.deserialize(cursor)))
|
||||
.map {
|
||||
case UrtOrderedCursor(_, id, Some(GapCursor), gapBoundaryId)
|
||||
if id.isEmpty || gapBoundaryId.isEmpty =>
|
||||
throw PipelineFailure(MalformedCursor, "Gap Cursor bounds not defined")
|
||||
case topCursor @ UrtOrderedCursor(_, _, Some(TopCursor), _) =>
|
||||
val queryTime = debugOptions.flatMap(_.requestTimeOverride).getOrElse(Time.now)
|
||||
topCursor.copy(initialSortIndex = SortIndexBuilder.timeToId(queryTime))
|
||||
case cursor => cursor
|
||||
}
|
||||
}
|
||||
|
||||
FollowingQuery(
|
||||
params = params,
|
||||
clientContext = request.clientContext,
|
||||
features = None,
|
||||
pipelineCursor = pipelineCursor,
|
||||
requestedMaxResults = Some(params(ServerMaxResultsParam)),
|
||||
debugOptions = debugOptions,
|
||||
deviceContext = context.deviceContext,
|
||||
seenTweetIds = context.seenTweetIds,
|
||||
dspClientContext = context.dspClientContext
|
||||
)
|
||||
}
|
||||
|
||||
override val pipelines: Seq[PipelineConfig] = Seq(followingMixerPipelineConfig)
|
||||
|
||||
override def pipelineSelector(
|
||||
query: FollowingQuery
|
||||
): ComponentIdentifier = followingMixerPipelineConfig.identifier
|
||||
|
||||
override val alerts: Seq[Alert] = Seq(
|
||||
SuccessRateAlert(
|
||||
notificationGroup = DefaultNotificationGroup,
|
||||
warnPredicate = TriggerIfBelow(99.9, 20, 30),
|
||||
criticalPredicate = TriggerIfBelow(99.9, 30, 30),
|
||||
),
|
||||
LatencyAlert(
|
||||
notificationGroup = DefaultNotificationGroup,
|
||||
percentile = P99,
|
||||
warnPredicate = TriggerIfLatencyAbove(1100.millis, 15, 30),
|
||||
criticalPredicate = TriggerIfLatencyAbove(1200.millis, 15, 30)
|
||||
),
|
||||
ThroughputAlert(
|
||||
notificationGroup = DefaultNotificationGroup,
|
||||
warnPredicate = TriggerIfAbove(18000),
|
||||
criticalPredicate = TriggerIfAbove(20000)
|
||||
),
|
||||
EmptyResponseRateAlert(
|
||||
notificationGroup = DefaultNotificationGroup,
|
||||
warnPredicate = TriggerIfAbove(65),
|
||||
criticalPredicate = TriggerIfAbove(80)
|
||||
)
|
||||
)
|
||||
|
||||
override val debugAccessPolicies: Set[AccessPolicy] = DefaultHomeMixerAccessPolicy
|
||||
}
|
Binary file not shown.
@ -1,65 +0,0 @@
|
||||
package com.twitter.home_mixer.product.following
|
||||
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.HomeWhoToFollowFeedbackActionInfoBuilder
|
||||
import com.twitter.home_mixer.functional_component.gate.DismissFatigueGate
|
||||
import com.twitter.home_mixer.functional_component.gate.TimelinesPersistenceStoreLastInjectionGate
|
||||
import com.twitter.home_mixer.model.HomeFeatures.DismissInfoFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.PersistenceEntriesFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.WhoToFollowExcludedUserIdsFeature
|
||||
import com.twitter.home_mixer.product.following.model.FollowingQuery
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam.EnableWhoToFollowCandidatePipelineParam
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam.WhoToFollowDisplayLocationParam
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam.WhoToFollowDisplayTypeIdParam
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam.WhoToFollowMinInjectionIntervalParam
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.ParamWhoToFollowModuleDisplayTypeBuilder
|
||||
import com.twitter.product_mixer.component_library.gate.NonEmptyCandidatesGate
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.who_to_follow_module.WhoToFollowArmCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.who_to_follow_module.WhoToFollowArmDependentCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.who_to_follow_module.WhoToFollowArmDependentCandidatePipelineConfigBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.common.CandidateScope
|
||||
import com.twitter.product_mixer.core.functional_component.configapi.StaticParam
|
||||
import com.twitter.product_mixer.core.functional_component.gate.BaseGate
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.timelineservice.model.rich.EntityIdType
|
||||
import com.twitter.timelineservice.suggests.thriftscala.SuggestType
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FollowingWhoToFollowCandidatePipelineConfigBuilder @Inject() (
|
||||
whoToFollowArmDependentCandidatePipelineConfigBuilder: WhoToFollowArmDependentCandidatePipelineConfigBuilder,
|
||||
homeWhoToFollowFeedbackActionInfoBuilder: HomeWhoToFollowFeedbackActionInfoBuilder) {
|
||||
|
||||
def build(
|
||||
requiredNonEmptyPipelines: CandidateScope
|
||||
): WhoToFollowArmDependentCandidatePipelineConfig[FollowingQuery] = {
|
||||
val gates: Seq[BaseGate[PipelineQuery]] = Seq(
|
||||
TimelinesPersistenceStoreLastInjectionGate(
|
||||
WhoToFollowMinInjectionIntervalParam,
|
||||
PersistenceEntriesFeature,
|
||||
EntityIdType.WhoToFollow
|
||||
),
|
||||
DismissFatigueGate(SuggestType.WhoToFollow, DismissInfoFeature),
|
||||
NonEmptyCandidatesGate(requiredNonEmptyPipelines)
|
||||
)
|
||||
|
||||
whoToFollowArmDependentCandidatePipelineConfigBuilder.build[FollowingQuery](
|
||||
identifier = WhoToFollowArmCandidatePipelineConfig.identifier,
|
||||
supportedClientParam = Some(EnableWhoToFollowCandidatePipelineParam),
|
||||
alerts = alerts,
|
||||
gates = gates,
|
||||
moduleDisplayTypeBuilder =
|
||||
ParamWhoToFollowModuleDisplayTypeBuilder(WhoToFollowDisplayTypeIdParam),
|
||||
feedbackActionInfoBuilder = Some(homeWhoToFollowFeedbackActionInfoBuilder),
|
||||
displayLocationParam = StaticParam(WhoToFollowDisplayLocationParam.default),
|
||||
excludedUserIdsFeature = Some(WhoToFollowExcludedUserIdsFeature),
|
||||
profileUserIdFeature = None
|
||||
)
|
||||
}
|
||||
|
||||
private val alerts = Seq(
|
||||
HomeMixerAlertConfig.BusinessHours.defaultSuccessRateAlert(70),
|
||||
HomeMixerAlertConfig.BusinessHours.defaultEmptyResponseRateAlert()
|
||||
)
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model/request",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/following/param",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/cursor",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/query/ads",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/pipeline/candidate/flexible_injection_pipeline",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"stringcenter/client",
|
||||
"stringcenter/client/src/main/java",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/cursor",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/query/ads",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,51 +0,0 @@
|
||||
package com.twitter.home_mixer.product.following.model
|
||||
|
||||
import com.twitter.adserver.thriftscala.HomeTimelineType
|
||||
import com.twitter.adserver.thriftscala.TimelineRequestParams
|
||||
import com.twitter.home_mixer.model.HomeAdsQuery
|
||||
import com.twitter.dspbidder.commons.{thriftscala => dsp}
|
||||
import com.twitter.home_mixer.model.request.DeviceContext
|
||||
import com.twitter.home_mixer.model.request.HasDeviceContext
|
||||
import com.twitter.home_mixer.model.request.HasSeenTweetIds
|
||||
import com.twitter.home_mixer.model.request.FollowingProduct
|
||||
import com.twitter.onboarding.task.service.{thriftscala => ots}
|
||||
import com.twitter.product_mixer.component_library.model.cursor.UrtOrderedCursor
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.flexible_injection_pipeline.transformer.HasFlipInjectionParams
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.model.marshalling.request._
|
||||
import com.twitter.product_mixer.core.pipeline.HasPipelineCursor
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.timelines.configapi.Params
|
||||
|
||||
case class FollowingQuery(
|
||||
override val params: Params,
|
||||
override val clientContext: ClientContext,
|
||||
override val pipelineCursor: Option[UrtOrderedCursor],
|
||||
override val requestedMaxResults: Option[Int],
|
||||
override val debugOptions: Option[DebugOptions],
|
||||
override val features: Option[FeatureMap],
|
||||
override val deviceContext: Option[DeviceContext],
|
||||
override val seenTweetIds: Option[Seq[Long]],
|
||||
override val dspClientContext: Option[dsp.DspClientContext])
|
||||
extends PipelineQuery
|
||||
with HasPipelineCursor[UrtOrderedCursor]
|
||||
with HasDeviceContext
|
||||
with HasSeenTweetIds
|
||||
with HasFlipInjectionParams
|
||||
with HomeAdsQuery {
|
||||
override val product: Product = FollowingProduct
|
||||
|
||||
override def withFeatureMap(features: FeatureMap): FollowingQuery =
|
||||
copy(features = Some(features))
|
||||
|
||||
override val timelineRequestParams: Option[TimelineRequestParams] =
|
||||
Some(TimelineRequestParams(homeTimelineType = Some(HomeTimelineType.HomeLatest)))
|
||||
|
||||
// Fields below are used for FLIP Injection in Onboarding Task Service (OTS)
|
||||
override val displayLocation: ots.DisplayLocation = ots.DisplayLocation.HomeLatestTimeline
|
||||
override val rankingDisablerWithLatestControlsAvailable: Option[Boolean] = None
|
||||
override val isEmptyState: Option[Boolean] = None
|
||||
override val isFirstRequestAfterSignup: Option[Boolean] = None
|
||||
override val isEndOfTimeline: Option[Boolean] = None
|
||||
override val timelineId: Option[Long] = None
|
||||
}
|
Binary file not shown.
@ -1,87 +0,0 @@
|
||||
package com.twitter.home_mixer.product.following.model
|
||||
|
||||
import com.twitter.product_mixer.core.product.guice.scope.ProductScoped
|
||||
import com.twitter.stringcenter.client.ExternalStringRegistry
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class HomeMixerExternalStrings @Inject() (
|
||||
@ProductScoped externalStringRegistryProvider: Provider[ExternalStringRegistry]) {
|
||||
val seeNewTweetsString =
|
||||
externalStringRegistryProvider.get().createProdString("SeeNewTweets")
|
||||
val tweetedString =
|
||||
externalStringRegistryProvider.get().createProdString("Tweeted")
|
||||
val muteUserString =
|
||||
externalStringRegistryProvider.get().createProdString("Feedback.muteUser")
|
||||
val blockUserString = externalStringRegistryProvider.get().createProdString("Feedback.blockUser")
|
||||
val unfollowUserString =
|
||||
externalStringRegistryProvider.get().createProdString("Feedback.unfollowUser")
|
||||
val unfollowUserConfirmationString =
|
||||
externalStringRegistryProvider.get().createProdString("Feedback.unfollowUserConfirmation")
|
||||
val reportTweetString =
|
||||
externalStringRegistryProvider.get().createProdString("Feedback.reportTweet")
|
||||
val dontLikeString = externalStringRegistryProvider.get().createProdString("Feedback.dontLike")
|
||||
val dontLikeConfirmationString =
|
||||
externalStringRegistryProvider.get().createProdString("Feedback.dontLikeConfirmation")
|
||||
val showFewerTweetsString =
|
||||
externalStringRegistryProvider.get().createProdString("Feedback.showFewerTweets")
|
||||
val showFewerTweetsConfirmationString =
|
||||
externalStringRegistryProvider.get().createProdString("Feedback.showFewerTweetsConfirmation")
|
||||
val showFewerRetweetsString =
|
||||
externalStringRegistryProvider.get().createProdString("Feedback.showFewerRetweets")
|
||||
val showFewerRetweetsConfirmationString =
|
||||
externalStringRegistryProvider.get().createProdString("Feedback.showFewerRetweetsConfirmation")
|
||||
val notRelevantString =
|
||||
externalStringRegistryProvider.get().createProdString("Feedback.notRelevant")
|
||||
val notRelevantConfirmationString =
|
||||
externalStringRegistryProvider.get().createProdString("Feedback.notRelevantConfirmation")
|
||||
|
||||
val socialContextOneUserLikedString =
|
||||
externalStringRegistryProvider.get().createProdString("SocialContext.oneUserLiked")
|
||||
val socialContextTwoUsersLikedString =
|
||||
externalStringRegistryProvider.get().createProdString("SocialContext.twoUsersLiked")
|
||||
val socialContextMoreUsersLikedString =
|
||||
externalStringRegistryProvider.get().createProdString("SocialContext.moreUsersLiked")
|
||||
val socialContextLikedByTimelineTitle =
|
||||
externalStringRegistryProvider.get().createProdString("SocialContext.likedByTimelineTitle")
|
||||
|
||||
val socialContextOneUserFollowsString =
|
||||
externalStringRegistryProvider.get().createProdString("SocialContext.oneUserFollows")
|
||||
val socialContextTwoUsersFollowString =
|
||||
externalStringRegistryProvider.get().createProdString("SocialContext.twoUsersFollow")
|
||||
val socialContextMoreUsersFollowString =
|
||||
externalStringRegistryProvider.get().createProdString("SocialContext.moreUsersFollow")
|
||||
val socialContextFollowedByTimelineTitle =
|
||||
externalStringRegistryProvider.get().createProdString("SocialContext.followedByTimelineTitle")
|
||||
|
||||
val socialContextYouMightLikeString =
|
||||
externalStringRegistryProvider.get().createProdString("SocialContext.youMightLike")
|
||||
|
||||
val socialContextExtendedReply =
|
||||
externalStringRegistryProvider.get().createProdString("SocialContext.extendedReply")
|
||||
val socialContextReceivedReply =
|
||||
externalStringRegistryProvider.get().createProdString("SocialContext.receivedReply")
|
||||
|
||||
val socialContextPopularVideoString =
|
||||
externalStringRegistryProvider.get().createProdString("SocialContext.popularVideo")
|
||||
|
||||
val socialContextPopularInYourAreaString =
|
||||
externalStringRegistryProvider.get().createProdString("PopgeoTweet.socialProof")
|
||||
|
||||
val listToFollowModuleHeaderString =
|
||||
externalStringRegistryProvider.get().createProdString("ListToFollowModule.header")
|
||||
val listToFollowModuleFooterString =
|
||||
externalStringRegistryProvider.get().createProdString("ListToFollowModule.footer")
|
||||
val pinnedListsModuleHeaderString =
|
||||
externalStringRegistryProvider.get().createProdString("PinnedListModule.header")
|
||||
val pinnedListsModuleEmptyStateMessageString =
|
||||
externalStringRegistryProvider.get().createProdString("PinnedListModule.emptyStateMessage")
|
||||
|
||||
val ownedSubscribedListsModuleHeaderString =
|
||||
externalStringRegistryProvider.get().createProdString("OwnedSubscribedListModule.header")
|
||||
val ownedSubscribedListsModuleEmptyStateMessageString =
|
||||
externalStringRegistryProvider
|
||||
.get().createProdString("OwnedSubscribedListModule.emptyStateMessage")
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"configapi/configapi-core/src/main/scala/com/twitter/timelines/configapi",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/param/decider",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/decorator/urt",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/product",
|
||||
"util/util-core/src/main/scala/com/twitter/conversions",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,85 +0,0 @@
|
||||
package com.twitter.home_mixer.product.following.param
|
||||
|
||||
import com.twitter.conversions.DurationOps._
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.WhoToFollowModuleDisplayType
|
||||
import com.twitter.timelines.configapi.DurationConversion
|
||||
import com.twitter.timelines.configapi.FSBoundedParam
|
||||
import com.twitter.timelines.configapi.FSEnumParam
|
||||
import com.twitter.timelines.configapi.FSParam
|
||||
import com.twitter.timelines.configapi.HasDurationConversion
|
||||
import com.twitter.util.Duration
|
||||
|
||||
object FollowingParam {
|
||||
val SupportedClientFSName = "following_supported_client"
|
||||
|
||||
object ServerMaxResultsParam
|
||||
extends FSBoundedParam[Int](
|
||||
name = "following_server_max_results",
|
||||
default = 100,
|
||||
min = 1,
|
||||
max = 500
|
||||
)
|
||||
|
||||
object EnableWhoToFollowCandidatePipelineParam
|
||||
extends FSParam[Boolean](
|
||||
name = "following_enable_who_to_follow",
|
||||
default = true
|
||||
)
|
||||
|
||||
object EnableAdsCandidatePipelineParam
|
||||
extends FSParam[Boolean](
|
||||
name = "following_enable_ads",
|
||||
default = true
|
||||
)
|
||||
|
||||
object EnableFlipInjectionModuleCandidatePipelineParam
|
||||
extends FSParam[Boolean](
|
||||
name = "following_enable_flip_inline_injection_module",
|
||||
default = true
|
||||
)
|
||||
|
||||
object FlipInlineInjectionModulePosition
|
||||
extends FSBoundedParam[Int](
|
||||
name = "following_flip_inline_injection_module_position",
|
||||
default = 0,
|
||||
min = 0,
|
||||
max = 1000
|
||||
)
|
||||
|
||||
object WhoToFollowPositionParam
|
||||
extends FSBoundedParam[Int](
|
||||
name = "following_who_to_follow_position",
|
||||
default = 5,
|
||||
min = 0,
|
||||
max = 99
|
||||
)
|
||||
|
||||
object WhoToFollowMinInjectionIntervalParam
|
||||
extends FSBoundedParam[Duration](
|
||||
"following_who_to_follow_min_injection_interval_in_minutes",
|
||||
default = 1800.minutes,
|
||||
min = 0.minutes,
|
||||
max = 6000.minutes)
|
||||
with HasDurationConversion {
|
||||
override val durationConversion: DurationConversion = DurationConversion.FromMinutes
|
||||
}
|
||||
|
||||
object WhoToFollowDisplayTypeIdParam
|
||||
extends FSEnumParam[WhoToFollowModuleDisplayType.type](
|
||||
name = "following_enable_who_to_follow_display_type_id",
|
||||
default = WhoToFollowModuleDisplayType.Vertical,
|
||||
enum = WhoToFollowModuleDisplayType
|
||||
)
|
||||
|
||||
object WhoToFollowDisplayLocationParam
|
||||
extends FSParam[String](
|
||||
name = "following_who_to_follow_display_location",
|
||||
default = "timeline_reverse_chron"
|
||||
)
|
||||
|
||||
object EnableFastAds
|
||||
extends FSParam[Boolean](
|
||||
name = "following_enable_fast_ads",
|
||||
default = true
|
||||
)
|
||||
}
|
Binary file not shown.
@ -1,34 +0,0 @@
|
||||
package com.twitter.home_mixer.product.following.param
|
||||
|
||||
import com.twitter.home_mixer.param.decider.DeciderKey
|
||||
import com.twitter.home_mixer.product.following.param.FollowingParam._
|
||||
import com.twitter.product_mixer.core.product.ProductParamConfig
|
||||
import com.twitter.servo.decider.DeciderKeyName
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FollowingParamConfig @Inject() () extends ProductParamConfig {
|
||||
override val enabledDeciderKey: DeciderKeyName = DeciderKey.EnableFollowingProduct
|
||||
override val supportedClientFSName: String = SupportedClientFSName
|
||||
|
||||
override val booleanFSOverrides =
|
||||
Seq(
|
||||
EnableFlipInjectionModuleCandidatePipelineParam,
|
||||
EnableWhoToFollowCandidatePipelineParam,
|
||||
EnableAdsCandidatePipelineParam,
|
||||
EnableFastAds,
|
||||
)
|
||||
|
||||
override val boundedIntFSOverrides = Seq(
|
||||
FlipInlineInjectionModulePosition,
|
||||
WhoToFollowPositionParam,
|
||||
ServerMaxResultsParam
|
||||
)
|
||||
|
||||
override val stringFSOverrides = Seq(WhoToFollowDisplayLocationParam)
|
||||
|
||||
override val boundedDurationFSOverrides = Seq(WhoToFollowMinInjectionIntervalParam)
|
||||
|
||||
override val enumFSOverrides = Seq(WhoToFollowDisplayTypeIdParam)
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"ads-injection/lib/src/main/scala/com/twitter/goldfinch/api",
|
||||
"finagle/finagle-memcached/src/main/scala",
|
||||
"finatra/inject/inject-core/src/main/scala",
|
||||
"finatra/inject/inject-core/src/main/scala/com/twitter/inject",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/candidate_pipeline",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/candidate_source",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/decorator",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/decorator/builder",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/decorator/urt/builder",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/feature_hydrator",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/filter",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/gate",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/scorer",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/selector",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/side_effect",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/marshaller/timelines",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model/request",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/param",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/following/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/candidate_source",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/feature_hydrator",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/filter",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/functional_component/gate",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/param",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/query_transformer",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/response_transformer",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/scorer",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/side_effect",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/scored_tweets/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/service",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/util",
|
||||
"home-mixer/thrift/src/main/thrift:thrift-scala",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/candidate_source/ads",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/candidate_source/earlybird",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/candidate_source/timeline_scorer",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/candidate_source/timeline_service",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/decorator/urt",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/candidate/ads",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/candidate/param_gated",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/candidate/tweet_is_nsfw",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/candidate/tweet_tweetypie",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/query/async",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/query/impressed_tweets",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/query/param_gated",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/query/social_graph",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/filter",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/gate",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate/ads",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/pipeline/candidate/ads",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/pipeline/candidate/flexible_injection_pipeline",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/pipeline/candidate/who_to_follow_module",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/pipeline/candidate/who_to_subscribe_module",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/premarshaller/urt",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/scorer/tweet_tlx",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/selector",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/selector/ads",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/side_effect",
|
||||
"product-mixer/core/src/main/java/com/twitter/product_mixer/core/product/guice/scope",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/product_pipeline",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/configapi",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/decorator",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/decorator/urt/builder",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/marshaller/request",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/marshaller/response/urt",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common/presentation",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/request",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/item",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline/candidate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline/mixer",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline/product",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline/recommendation",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline/scoring",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/product",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/product/guice",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/quality_factor",
|
||||
"src/java/com/twitter/search/common/util/lang",
|
||||
"src/scala/com/twitter/suggests/controller_data",
|
||||
"src/thrift/com/twitter/search:earlybird-scala",
|
||||
"src/thrift/com/twitter/search/common:constants-java",
|
||||
"src/thrift/com/twitter/timelines/render:thrift-scala",
|
||||
"src/thrift/com/twitter/timelines/suggests/common:poly_data_record-java",
|
||||
"src/thrift/com/twitter/timelineservice:thrift-scala",
|
||||
"stitch/stitch-tweetypie",
|
||||
"timelinemixer/server/src/main/scala/com/twitter/timelinemixer/injection/model/candidate",
|
||||
"timelines/src/main/scala/com/twitter/timelines/injection/scribe",
|
||||
"timelines/src/main/scala/com/twitter/timelines/model/candidate",
|
||||
],
|
||||
exports = [
|
||||
"src/thrift/com/twitter/timelines/render:thrift-scala",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,94 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.candidate.param_gated.ParamGatedCandidateFeatureHydrator
|
||||
import com.twitter.adserver.{thriftscala => ads}
|
||||
import com.twitter.home_mixer.functional_component.decorator.builder.HomeAdsClientEventDetailsBuilder
|
||||
import com.twitter.home_mixer.functional_component.gate.ExcludeSoftUserGate
|
||||
import com.twitter.home_mixer.param.HomeGlobalParams
|
||||
import com.twitter.home_mixer.param.HomeGlobalParams.EnableAdvertiserBrandSafetySettingsFeatureHydratorParam
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.AdsNumOrganicItemsParam
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig
|
||||
import com.twitter.product_mixer.component_library.candidate_source.ads.AdsProdThriftCandidateSource
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.UrtItemCandidateDecorator
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.contextual_ref.ContextualTweetRefBuilder
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.ad.AdsCandidateUrtItemBuilder
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.metadata.ClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.candidate.ads.AdvertiserBrandSafetySettingsFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.model.candidate.ads.AdsCandidate
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.AdsCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.AdsCandidatePipelineConfigBuilder
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.StaticAdsDisplayLocationBuilder
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.ValidAdImpressionIdFilter
|
||||
import com.twitter.product_mixer.core.functional_component.common.CandidateScope
|
||||
import com.twitter.product_mixer.core.gate.ParamNotGate
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidatePipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.rtf.safety_level.TimelineHomePromotedHydrationSafetyLevel
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.contextual_ref.TweetHydrationContext
|
||||
import com.twitter.timelines.injection.scribe.InjectionScribeUtil
|
||||
import com.twitter.timelineservice.suggests.{thriftscala => st}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ForYouAdsCandidatePipelineBuilder @Inject() (
|
||||
adsCandidatePipelineConfigBuilder: AdsCandidatePipelineConfigBuilder,
|
||||
adsCandidateSource: AdsProdThriftCandidateSource,
|
||||
advertiserBrandSafetySettingsFeatureHydrator: AdvertiserBrandSafetySettingsFeatureHydrator[
|
||||
ForYouQuery,
|
||||
AdsCandidate
|
||||
]) {
|
||||
|
||||
private val identifier: CandidatePipelineIdentifier = CandidatePipelineIdentifier("ForYouAds")
|
||||
|
||||
private val suggestType = st.SuggestType.Promoted
|
||||
|
||||
private val clientEventInfoBuilder = ClientEventInfoBuilder(
|
||||
component = InjectionScribeUtil.scribeComponent(suggestType).get,
|
||||
detailsBuilder = Some(HomeAdsClientEventDetailsBuilder(Some(suggestType.name)))
|
||||
)
|
||||
|
||||
private val contextualTweetRefBuilder = ContextualTweetRefBuilder(
|
||||
TweetHydrationContext(
|
||||
safetyLevelOverride = Some(TimelineHomePromotedHydrationSafetyLevel),
|
||||
outerTweetContext = None
|
||||
))
|
||||
|
||||
private val decorator = UrtItemCandidateDecorator(
|
||||
AdsCandidateUrtItemBuilder(
|
||||
tweetClientEventInfoBuilder = Some(clientEventInfoBuilder),
|
||||
contextualTweetRefBuilder = Some(contextualTweetRefBuilder)
|
||||
))
|
||||
|
||||
private val alerts = Seq(
|
||||
HomeMixerAlertConfig.BusinessHours.defaultSuccessRateAlert(),
|
||||
HomeMixerAlertConfig.BusinessHours.defaultEmptyResponseRateAlert()
|
||||
)
|
||||
|
||||
def build(
|
||||
organicCandidatePipelines: Option[CandidateScope] = None
|
||||
): AdsCandidatePipelineConfig[ForYouQuery] =
|
||||
adsCandidatePipelineConfigBuilder.build[ForYouQuery](
|
||||
adsCandidateSource = adsCandidateSource,
|
||||
identifier = identifier,
|
||||
adsDisplayLocationBuilder = StaticAdsDisplayLocationBuilder(ads.DisplayLocation.TimelineHome),
|
||||
estimateNumOrganicItems = _.params(AdsNumOrganicItemsParam).toShort,
|
||||
gates = Seq(
|
||||
ParamNotGate(
|
||||
name = "AdsDisableInjectionBasedOnUserRole",
|
||||
param = HomeGlobalParams.AdsDisableInjectionBasedOnUserRoleParam
|
||||
),
|
||||
ExcludeSoftUserGate
|
||||
),
|
||||
filters = Seq(ValidAdImpressionIdFilter),
|
||||
postFilterFeatureHydration = Seq(
|
||||
ParamGatedCandidateFeatureHydrator(
|
||||
EnableAdvertiserBrandSafetySettingsFeatureHydratorParam,
|
||||
advertiserBrandSafetySettingsFeatureHydrator
|
||||
)
|
||||
),
|
||||
decorator = Some(decorator),
|
||||
alerts = alerts,
|
||||
urtRequest = Some(true),
|
||||
)
|
||||
}
|
Binary file not shown.
@ -1,111 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.adserver.{thriftscala => ads}
|
||||
import com.twitter.home_mixer.functional_component.decorator.builder.HomeAdsClientEventDetailsBuilder
|
||||
import com.twitter.home_mixer.functional_component.gate.ExcludeSoftUserGate
|
||||
import com.twitter.home_mixer.model.HomeFeatures.TweetLanguageFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.TweetTextFeature
|
||||
import com.twitter.home_mixer.param.HomeGlobalParams
|
||||
import com.twitter.home_mixer.param.HomeGlobalParams.EnableAdvertiserBrandSafetySettingsFeatureHydratorParam
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig
|
||||
import com.twitter.home_mixer.util.CandidatesUtil
|
||||
import com.twitter.product_mixer.component_library.candidate_source.ads.AdsProdThriftCandidateSource
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.UrtItemCandidateDecorator
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.contextual_ref.ContextualTweetRefBuilder
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.ad.AdsCandidateUrtItemBuilder
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.metadata.ClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.candidate.ads.AdvertiserBrandSafetySettingsFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.candidate.param_gated.ParamGatedCandidateFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.gate.NonEmptyCandidatesGate
|
||||
import com.twitter.product_mixer.component_library.model.candidate.ads.AdsCandidate
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.AdsDependentCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.AdsDependentCandidatePipelineConfigBuilder
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.CountTruncatedItemCandidatesFromPipelines
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.StaticAdsDisplayLocationBuilder
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.TruncatedPipelineScopedOrganicItems
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.ads.ValidAdImpressionIdFilter
|
||||
import com.twitter.product_mixer.core.functional_component.common.CandidateScope
|
||||
import com.twitter.product_mixer.core.gate.ParamNotGate
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidatePipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.rtf.safety_level.TimelineHomePromotedHydrationSafetyLevel
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.contextual_ref.TweetHydrationContext
|
||||
import com.twitter.timelines.injection.scribe.InjectionScribeUtil
|
||||
import com.twitter.timelineservice.suggests.{thriftscala => st}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ForYouAdsDependentCandidatePipelineBuilder @Inject() (
|
||||
adsCandidatePipelineConfigBuilder: AdsDependentCandidatePipelineConfigBuilder,
|
||||
adsCandidateSource: AdsProdThriftCandidateSource,
|
||||
advertiserBrandSafetySettingsFeatureHydrator: AdvertiserBrandSafetySettingsFeatureHydrator[
|
||||
ForYouQuery,
|
||||
AdsCandidate
|
||||
]) {
|
||||
|
||||
private val identifier: CandidatePipelineIdentifier =
|
||||
CandidatePipelineIdentifier("ForYouAdsDependent")
|
||||
|
||||
private val suggestType = st.SuggestType.Promoted
|
||||
|
||||
private val MaxOrganicTweets = 35
|
||||
|
||||
private val clientEventInfoBuilder = ClientEventInfoBuilder(
|
||||
component = InjectionScribeUtil.scribeComponent(suggestType).get,
|
||||
detailsBuilder = Some(HomeAdsClientEventDetailsBuilder(Some(suggestType.name)))
|
||||
)
|
||||
|
||||
private val contextualTweetRefBuilder = ContextualTweetRefBuilder(
|
||||
TweetHydrationContext(
|
||||
safetyLevelOverride = Some(TimelineHomePromotedHydrationSafetyLevel),
|
||||
outerTweetContext = None
|
||||
))
|
||||
|
||||
private val decorator = UrtItemCandidateDecorator(
|
||||
AdsCandidateUrtItemBuilder(
|
||||
tweetClientEventInfoBuilder = Some(clientEventInfoBuilder),
|
||||
contextualTweetRefBuilder = Some(contextualTweetRefBuilder)
|
||||
))
|
||||
|
||||
private val alerts = Seq(
|
||||
HomeMixerAlertConfig.BusinessHours.defaultSuccessRateAlert(),
|
||||
HomeMixerAlertConfig.BusinessHours.defaultEmptyResponseRateAlert()
|
||||
)
|
||||
|
||||
def build(
|
||||
organicCandidatePipelines: CandidateScope
|
||||
): AdsDependentCandidatePipelineConfig[ForYouQuery] =
|
||||
adsCandidatePipelineConfigBuilder.build[ForYouQuery](
|
||||
adsCandidateSource = adsCandidateSource,
|
||||
identifier = identifier,
|
||||
adsDisplayLocationBuilder = StaticAdsDisplayLocationBuilder(ads.DisplayLocation.TimelineHome),
|
||||
getOrganicItems = TruncatedPipelineScopedOrganicItems(
|
||||
pipelines = organicCandidatePipelines,
|
||||
textFeature = TweetTextFeature,
|
||||
languageFeature = TweetLanguageFeature,
|
||||
ordering = CandidatesUtil.scoreOrdering,
|
||||
maxCount = MaxOrganicTweets
|
||||
),
|
||||
countNumOrganicItems =
|
||||
CountTruncatedItemCandidatesFromPipelines(organicCandidatePipelines, MaxOrganicTweets),
|
||||
gates = Seq(
|
||||
ParamNotGate(
|
||||
name = "AdsDisableInjectionBasedOnUserRole",
|
||||
param = HomeGlobalParams.AdsDisableInjectionBasedOnUserRoleParam
|
||||
),
|
||||
ExcludeSoftUserGate,
|
||||
NonEmptyCandidatesGate(organicCandidatePipelines)
|
||||
),
|
||||
filters = Seq(ValidAdImpressionIdFilter),
|
||||
postFilterFeatureHydration = Seq(
|
||||
ParamGatedCandidateFeatureHydrator(
|
||||
EnableAdvertiserBrandSafetySettingsFeatureHydratorParam,
|
||||
advertiserBrandSafetySettingsFeatureHydrator
|
||||
)
|
||||
),
|
||||
decorator = Some(decorator),
|
||||
alerts = alerts,
|
||||
urtRequest = Some(true)
|
||||
)
|
||||
}
|
Binary file not shown.
@ -1,138 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.home_mixer.candidate_pipeline.ConversationServiceResponseFeatureTransformer
|
||||
import com.twitter.home_mixer.functional_component.decorator.HomeConversationServiceCandidateDecorator
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.HomeFeedbackActionInfoBuilder
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator.InNetworkFeatureHydrator
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator.NamesFeatureHydrator
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator.TweetypieFeatureHydrator
|
||||
import com.twitter.home_mixer.functional_component.filter.InvalidConversationModuleFilter
|
||||
import com.twitter.home_mixer.functional_component.filter.InvalidSubscriptionTweetFilter
|
||||
import com.twitter.home_mixer.functional_component.filter.PreviouslyServedTweetsFilter
|
||||
import com.twitter.home_mixer.functional_component.filter.RetweetDeduplicationFilter
|
||||
import com.twitter.home_mixer.model.HomeFeatures.IsHydratedFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.QuotedTweetDroppedFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.TimelineServiceTweetsFeature
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig
|
||||
import com.twitter.product_mixer.component_library.candidate_source.tweetconvosvc.ConversationServiceCandidateSource
|
||||
import com.twitter.product_mixer.component_library.candidate_source.tweetconvosvc.ConversationServiceCandidateSourceRequest
|
||||
import com.twitter.product_mixer.component_library.candidate_source.tweetconvosvc.TweetWithConversationMetadata
|
||||
import com.twitter.product_mixer.component_library.filter.FeatureFilter
|
||||
import com.twitter.product_mixer.component_library.filter.PredicateFeatureFilter
|
||||
import com.twitter.product_mixer.component_library.gate.NoCandidatesGate
|
||||
import com.twitter.product_mixer.component_library.gate.NonEmptySeqFeatureGate
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.BaseCandidateSource
|
||||
import com.twitter.product_mixer.core.functional_component.common.SpecificPipelines
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.CandidateDecorator
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.BaseCandidateFeatureHydrator
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.gate.BaseGate
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidateFeatureTransformer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidatePipelineResultsTransformer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.DependentCandidatePipelineQueryTransformer
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidatePipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.DependentCandidatePipelineConfig
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Candidate Pipeline Config that fetches Tweet ancestors from Conversation Service Candidate Source
|
||||
*/
|
||||
@Singleton
|
||||
class ForYouConversationServiceCandidatePipelineConfig @Inject() (
|
||||
forYouScoredTweetsCandidatePipelineConfig: ForYouScoredTweetsCandidatePipelineConfig,
|
||||
forYouTimelineScorerCandidatePipelineConfig: ForYouTimelineScorerCandidatePipelineConfig,
|
||||
conversationServiceCandidateSource: ConversationServiceCandidateSource,
|
||||
tweetypieFeatureHydrator: TweetypieFeatureHydrator,
|
||||
namesFeatureHydrator: NamesFeatureHydrator,
|
||||
invalidSubscriptionTweetFilter: InvalidSubscriptionTweetFilter,
|
||||
homeFeedbackActionInfoBuilder: HomeFeedbackActionInfoBuilder)
|
||||
extends DependentCandidatePipelineConfig[
|
||||
ForYouQuery,
|
||||
ConversationServiceCandidateSourceRequest,
|
||||
TweetWithConversationMetadata,
|
||||
TweetCandidate
|
||||
] {
|
||||
|
||||
override val identifier: CandidatePipelineIdentifier =
|
||||
CandidatePipelineIdentifier("ForYouConversationService")
|
||||
|
||||
override val gates: Seq[BaseGate[ForYouQuery]] = Seq(
|
||||
NoCandidatesGate(
|
||||
SpecificPipelines(
|
||||
forYouTimelineScorerCandidatePipelineConfig.identifier,
|
||||
forYouScoredTweetsCandidatePipelineConfig.identifier
|
||||
)
|
||||
),
|
||||
NonEmptySeqFeatureGate(TimelineServiceTweetsFeature)
|
||||
)
|
||||
|
||||
override val candidateSource: BaseCandidateSource[
|
||||
ConversationServiceCandidateSourceRequest,
|
||||
TweetWithConversationMetadata
|
||||
] = conversationServiceCandidateSource
|
||||
|
||||
override val queryTransformer: DependentCandidatePipelineQueryTransformer[
|
||||
ForYouQuery,
|
||||
ConversationServiceCandidateSourceRequest
|
||||
] = { (query, candidates) =>
|
||||
val timelineServiceTweets = query.features
|
||||
.map(_.getOrElse(TimelineServiceTweetsFeature, Seq.empty)).getOrElse(Seq.empty)
|
||||
|
||||
val tweetsWithConversationMetadata = timelineServiceTweets.map { id =>
|
||||
TweetWithConversationMetadata(
|
||||
tweetId = id,
|
||||
userId = None,
|
||||
sourceTweetId = None,
|
||||
sourceUserId = None,
|
||||
inReplyToTweetId = None,
|
||||
conversationId = None,
|
||||
ancestors = Seq.empty
|
||||
)
|
||||
}
|
||||
ConversationServiceCandidateSourceRequest(tweetsWithConversationMetadata)
|
||||
}
|
||||
|
||||
override val featuresFromCandidateSourceTransformers: Seq[
|
||||
CandidateFeatureTransformer[TweetWithConversationMetadata]
|
||||
] = Seq(ConversationServiceResponseFeatureTransformer)
|
||||
|
||||
override val resultTransformer: CandidatePipelineResultsTransformer[
|
||||
TweetWithConversationMetadata,
|
||||
TweetCandidate
|
||||
] = { sourceResult => TweetCandidate(id = sourceResult.tweetId) }
|
||||
|
||||
override val preFilterFeatureHydrationPhase1: Seq[
|
||||
BaseCandidateFeatureHydrator[ForYouQuery, TweetCandidate, _]
|
||||
] = Seq(
|
||||
InNetworkFeatureHydrator,
|
||||
tweetypieFeatureHydrator
|
||||
)
|
||||
|
||||
override def filters: Seq[Filter[ForYouQuery, TweetCandidate]] = Seq(
|
||||
PreviouslyServedTweetsFilter,
|
||||
RetweetDeduplicationFilter,
|
||||
FeatureFilter.fromFeature(FilterIdentifier("TweetypieHydrated"), IsHydratedFeature),
|
||||
PredicateFeatureFilter.fromPredicate(
|
||||
FilterIdentifier("QuotedTweetDropped"),
|
||||
shouldKeepCandidate = { features => !features.getOrElse(QuotedTweetDroppedFeature, false) }
|
||||
),
|
||||
invalidSubscriptionTweetFilter,
|
||||
InvalidConversationModuleFilter
|
||||
)
|
||||
|
||||
override val postFilterFeatureHydration: Seq[
|
||||
BaseCandidateFeatureHydrator[ForYouQuery, TweetCandidate, _]
|
||||
] = Seq(namesFeatureHydrator)
|
||||
|
||||
override val decorator: Option[CandidateDecorator[ForYouQuery, TweetCandidate]] =
|
||||
HomeConversationServiceCandidateDecorator(homeFeedbackActionInfoBuilder)
|
||||
|
||||
override val alerts = Seq(
|
||||
HomeMixerAlertConfig.BusinessHours.defaultSuccessRateAlert(),
|
||||
HomeMixerAlertConfig.BusinessHours.defaultEmptyResponseRateAlert()
|
||||
)
|
||||
}
|
Binary file not shown.
@ -1,142 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.conversions.DurationOps._
|
||||
import com.twitter.home_mixer.marshaller.timelines.ChronologicalCursorUnmarshaller
|
||||
import com.twitter.home_mixer.model.request.ForYouProduct
|
||||
import com.twitter.home_mixer.model.request.ForYouProductContext
|
||||
import com.twitter.home_mixer.model.request.HomeMixerRequest
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.EnablePushToHomeMixerPipelineParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.EnableScoredTweetsMixerPipelineParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.ServerMaxResultsParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParamConfig
|
||||
import com.twitter.home_mixer.service.HomeMixerAccessPolicy.DefaultHomeMixerAccessPolicy
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig.DefaultNotificationGroup
|
||||
import com.twitter.product_mixer.component_library.model.cursor.UrtOrderedCursor
|
||||
import com.twitter.product_mixer.component_library.premarshaller.cursor.UrtCursorSerializer
|
||||
import com.twitter.product_mixer.core.functional_component.common.access_policy.AccessPolicy
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.EmptyResponseRateAlert
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.LatencyAlert
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.P99
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.SuccessRateAlert
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.ThroughputAlert
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.predicate.TriggerIfAbove
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.predicate.TriggerIfBelow
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.predicate.TriggerIfLatencyAbove
|
||||
import com.twitter.product_mixer.core.model.common.identifier.ComponentIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.ProductPipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.request.Product
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.operation.TopCursor
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineConfig
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.BadRequest
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.PipelineFailure
|
||||
import com.twitter.product_mixer.core.pipeline.product.ProductPipelineConfig
|
||||
import com.twitter.product_mixer.core.product.ProductParamConfig
|
||||
import com.twitter.product_mixer.core.util.SortIndexBuilder
|
||||
import com.twitter.timelines.configapi.Params
|
||||
import com.twitter.timelines.render.{thriftscala => urt}
|
||||
import com.twitter.timelines.util.RequestCursorSerializer
|
||||
import com.twitter.util.Time
|
||||
import com.twitter.util.Try
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ForYouProductPipelineConfig @Inject() (
|
||||
forYouTimelineScorerMixerPipelineConfig: ForYouTimelineScorerMixerPipelineConfig,
|
||||
forYouScoredTweetsMixerPipelineConfig: ForYouScoredTweetsMixerPipelineConfig,
|
||||
forYouPushToHomeMixerPipelineConfig: ForYouPushToHomeMixerPipelineConfig,
|
||||
forYouParamConfig: ForYouParamConfig)
|
||||
extends ProductPipelineConfig[HomeMixerRequest, ForYouQuery, urt.TimelineResponse] {
|
||||
|
||||
override val identifier: ProductPipelineIdentifier = ProductPipelineIdentifier("ForYou")
|
||||
|
||||
override val product: Product = ForYouProduct
|
||||
|
||||
override val paramConfig: ProductParamConfig = forYouParamConfig
|
||||
|
||||
override def pipelineQueryTransformer(
|
||||
request: HomeMixerRequest,
|
||||
params: Params
|
||||
): ForYouQuery = {
|
||||
val context = request.productContext match {
|
||||
case Some(context: ForYouProductContext) => context
|
||||
case _ => throw PipelineFailure(BadRequest, "ForYouProductContext not found")
|
||||
}
|
||||
|
||||
val debugOptions = request.debugParams.flatMap(_.debugOptions)
|
||||
|
||||
/**
|
||||
* Unlike other clients, newly created tweets on Android have the sort index set to the current
|
||||
* time instead of the top sort index + 1, so these tweets get stuck at the top of the timeline
|
||||
* if subsequent timeline responses use the sort index from the previous response instead of
|
||||
* the current time.
|
||||
*/
|
||||
val pipelineCursor = request.serializedRequestCursor.flatMap { cursor =>
|
||||
Try(UrtCursorSerializer.deserializeOrderedCursor(cursor))
|
||||
.getOrElse(ChronologicalCursorUnmarshaller(RequestCursorSerializer.deserialize(cursor)))
|
||||
.map {
|
||||
case topCursor @ UrtOrderedCursor(_, _, Some(TopCursor), _) =>
|
||||
val queryTime = debugOptions.flatMap(_.requestTimeOverride).getOrElse(Time.now)
|
||||
topCursor.copy(initialSortIndex = SortIndexBuilder.timeToId(queryTime))
|
||||
case cursor => cursor
|
||||
}
|
||||
}
|
||||
|
||||
ForYouQuery(
|
||||
params = params,
|
||||
clientContext = request.clientContext,
|
||||
features = None,
|
||||
pipelineCursor = pipelineCursor,
|
||||
requestedMaxResults = Some(params(ServerMaxResultsParam)),
|
||||
debugOptions = debugOptions,
|
||||
deviceContext = context.deviceContext,
|
||||
seenTweetIds = context.seenTweetIds,
|
||||
dspClientContext = context.dspClientContext,
|
||||
pushToHomeTweetId = context.pushToHomeTweetId
|
||||
)
|
||||
}
|
||||
|
||||
override val pipelines: Seq[PipelineConfig] = Seq(
|
||||
forYouTimelineScorerMixerPipelineConfig,
|
||||
forYouScoredTweetsMixerPipelineConfig,
|
||||
forYouPushToHomeMixerPipelineConfig
|
||||
)
|
||||
|
||||
override def pipelineSelector(
|
||||
query: ForYouQuery
|
||||
): ComponentIdentifier = {
|
||||
if (query.pushToHomeTweetId.isDefined && query.params(EnablePushToHomeMixerPipelineParam))
|
||||
forYouPushToHomeMixerPipelineConfig.identifier
|
||||
else if (query.params(EnableScoredTweetsMixerPipelineParam))
|
||||
forYouScoredTweetsMixerPipelineConfig.identifier
|
||||
else forYouTimelineScorerMixerPipelineConfig.identifier
|
||||
}
|
||||
|
||||
override val alerts: Seq[Alert] = Seq(
|
||||
SuccessRateAlert(
|
||||
notificationGroup = DefaultNotificationGroup,
|
||||
warnPredicate = TriggerIfBelow(99.9, 20, 30),
|
||||
criticalPredicate = TriggerIfBelow(99.9, 30, 30),
|
||||
),
|
||||
LatencyAlert(
|
||||
notificationGroup = DefaultNotificationGroup,
|
||||
percentile = P99,
|
||||
warnPredicate = TriggerIfLatencyAbove(2300.millis, 15, 30),
|
||||
criticalPredicate = TriggerIfLatencyAbove(2800.millis, 15, 30)
|
||||
),
|
||||
ThroughputAlert(
|
||||
notificationGroup = DefaultNotificationGroup,
|
||||
warnPredicate = TriggerIfAbove(70000),
|
||||
criticalPredicate = TriggerIfAbove(80000)
|
||||
),
|
||||
EmptyResponseRateAlert(
|
||||
notificationGroup = DefaultNotificationGroup,
|
||||
warnPredicate = TriggerIfAbove(2),
|
||||
criticalPredicate = TriggerIfAbove(3)
|
||||
)
|
||||
)
|
||||
|
||||
override val debugAccessPolicies: Set[AccessPolicy] = DefaultHomeMixerAccessPolicy
|
||||
}
|
Binary file not shown.
@ -1,74 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.UrtDomainMarshaller
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.AddEntriesInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ClearCacheInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.OrderedBottomCursorBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.OrderedTopCursorBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ParamGatedIncludeInstruction
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.StaticTimelineScribeConfigBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.UrtMetadataBuilder
|
||||
import com.twitter.product_mixer.component_library.selector.InsertAppendResults
|
||||
import com.twitter.product_mixer.core.functional_component.marshaller.TransportMarshaller
|
||||
import com.twitter.product_mixer.core.functional_component.marshaller.response.urt.UrtTransportMarshaller
|
||||
import com.twitter.product_mixer.core.functional_component.premarshaller.DomainMarshaller
|
||||
import com.twitter.product_mixer.core.functional_component.selector.Selector
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.MixerPipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.Timeline
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineScribeConfig
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.tweet.TweetItem
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.CandidatePipelineConfig
|
||||
import com.twitter.product_mixer.core.pipeline.mixer.MixerPipelineConfig
|
||||
import com.twitter.timelines.render.{thriftscala => urt}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ForYouPushToHomeMixerPipelineConfig @Inject() (
|
||||
forYouPushToHomeTweetCandidatePipelineConfig: ForYouPushToHomeTweetCandidatePipelineConfig,
|
||||
urtTransportMarshaller: UrtTransportMarshaller)
|
||||
extends MixerPipelineConfig[ForYouQuery, Timeline, urt.TimelineResponse] {
|
||||
|
||||
override val identifier: MixerPipelineIdentifier = MixerPipelineIdentifier("ForYouPushToHome")
|
||||
|
||||
override val candidatePipelines: Seq[CandidatePipelineConfig[ForYouQuery, _, _, _]] =
|
||||
Seq(forYouPushToHomeTweetCandidatePipelineConfig)
|
||||
|
||||
override val resultSelectors: Seq[Selector[ForYouQuery]] =
|
||||
Seq(InsertAppendResults(forYouPushToHomeTweetCandidatePipelineConfig.identifier))
|
||||
|
||||
override val domainMarshaller: DomainMarshaller[ForYouQuery, Timeline] = {
|
||||
val instructionBuilders = Seq(
|
||||
ClearCacheInstructionBuilder(
|
||||
ParamGatedIncludeInstruction(ForYouParam.EnableClearCacheOnPushToHome)),
|
||||
AddEntriesInstructionBuilder())
|
||||
|
||||
val idSelector: PartialFunction[UniversalNoun[_], Long] = { case item: TweetItem => item.id }
|
||||
val topCursorBuilder = OrderedTopCursorBuilder(idSelector)
|
||||
val bottomCursorBuilder = OrderedBottomCursorBuilder(idSelector)
|
||||
|
||||
val metadataBuilder = UrtMetadataBuilder(
|
||||
title = None,
|
||||
scribeConfigBuilder = Some(
|
||||
StaticTimelineScribeConfigBuilder(
|
||||
TimelineScribeConfig(
|
||||
page = Some("for_you_push_to_home"),
|
||||
section = None,
|
||||
entityToken = None)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
UrtDomainMarshaller(
|
||||
instructionBuilders = instructionBuilders,
|
||||
metadataBuilder = Some(metadataBuilder),
|
||||
cursorBuilders = Seq(topCursorBuilder, bottomCursorBuilder)
|
||||
)
|
||||
}
|
||||
|
||||
override val transportMarshaller: TransportMarshaller[Timeline, urt.TimelineResponse] =
|
||||
urtTransportMarshaller
|
||||
}
|
Binary file not shown.
@ -1,81 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.home_mixer.functional_component.decorator.builder.HomeClientEventInfoBuilder
|
||||
import com.twitter.home_mixer.model.HomeFeatures.SuggestTypeFeature
|
||||
import com.twitter.home_mixer.product.for_you.functional_component.gate.PushToHomeRequestGate
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.UrtItemCandidateDecorator
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.tweet.TweetCandidateUrtItemBuilder
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSource
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.PassthroughCandidateSource
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.CandidateDecorator
|
||||
import com.twitter.product_mixer.core.functional_component.gate.Gate
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidateFeatureTransformer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidatePipelineQueryTransformer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidatePipelineResultsTransformer
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidatePipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.TransformerIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.CandidatePipelineConfig
|
||||
import com.twitter.timelineservice.suggests.{thriftscala => st}
|
||||
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ForYouPushToHomeTweetCandidatePipelineConfig @Inject() ()
|
||||
extends CandidatePipelineConfig[
|
||||
ForYouQuery,
|
||||
ForYouQuery,
|
||||
TweetCandidate,
|
||||
TweetCandidate
|
||||
] {
|
||||
|
||||
override val identifier: CandidatePipelineIdentifier =
|
||||
CandidatePipelineIdentifier("ForYouPushToHomeTweet")
|
||||
|
||||
override val gates: Seq[Gate[ForYouQuery]] = Seq(PushToHomeRequestGate)
|
||||
|
||||
override val queryTransformer: CandidatePipelineQueryTransformer[
|
||||
ForYouQuery,
|
||||
ForYouQuery
|
||||
] = identity
|
||||
|
||||
override val featuresFromCandidateSourceTransformers: Seq[
|
||||
CandidateFeatureTransformer[TweetCandidate]
|
||||
] = Seq(new CandidateFeatureTransformer[TweetCandidate] {
|
||||
override def features: Set[Feature[_, _]] = Set(SuggestTypeFeature)
|
||||
|
||||
override val identifier: TransformerIdentifier =
|
||||
TransformerIdentifier("ForYouPushToHomeTweet")
|
||||
|
||||
override def transform(input: TweetCandidate): FeatureMap =
|
||||
FeatureMapBuilder().add(SuggestTypeFeature, Some(st.SuggestType.Magicrec)).build()
|
||||
})
|
||||
|
||||
override val resultTransformer: CandidatePipelineResultsTransformer[
|
||||
TweetCandidate,
|
||||
TweetCandidate
|
||||
] = identity
|
||||
|
||||
override val candidateSource: CandidateSource[
|
||||
ForYouQuery,
|
||||
TweetCandidate
|
||||
] = PassthroughCandidateSource(
|
||||
CandidateSourceIdentifier("PushToHomeTweet"),
|
||||
{ query => query.pushToHomeTweetId.toSeq.map(TweetCandidate(_)) }
|
||||
)
|
||||
|
||||
override val decorator: Option[
|
||||
CandidateDecorator[ForYouQuery, TweetCandidate]
|
||||
] = {
|
||||
val tweetItemBuilder = TweetCandidateUrtItemBuilder(
|
||||
clientEventInfoBuilder = HomeClientEventInfoBuilder()
|
||||
)
|
||||
Some(UrtItemCandidateDecorator(tweetItemBuilder))
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,155 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.home_mixer.functional_component.decorator.builder.HomeClientEventInfoBuilder
|
||||
import com.twitter.home_mixer.functional_component.decorator.builder.HomeConversationModuleMetadataBuilder
|
||||
import com.twitter.home_mixer.functional_component.decorator.builder.HomeTimelinesScoreInfoBuilder
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.HomeFeedbackActionInfoBuilder
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.HomeTweetSocialContextBuilder
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator.InNetworkFeatureHydrator
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator.NamesFeatureHydrator
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator.TweetypieFeatureHydrator
|
||||
import com.twitter.home_mixer.functional_component.filter.InvalidConversationModuleFilter
|
||||
import com.twitter.home_mixer.functional_component.filter.InvalidSubscriptionTweetFilter
|
||||
import com.twitter.home_mixer.functional_component.gate.SupportedLanguagesGate
|
||||
import com.twitter.home_mixer.model.HomeFeatures.ConversationModuleFocalTweetIdFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.InNetworkFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.IsHydratedFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.IsNsfwFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.QuotedTweetDroppedFeature
|
||||
import com.twitter.home_mixer.product.for_you.candidate_source.ScoredTweetWithConversationMetadata
|
||||
import com.twitter.home_mixer.product.for_you.candidate_source.ScoredTweetsProductCandidateSource
|
||||
import com.twitter.home_mixer.product.for_you.feature_hydrator.FocalTweetFeatureHydrator
|
||||
import com.twitter.home_mixer.product.for_you.filter.SocialContextFilter
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.EnableScoredTweetsCandidatePipelineParam
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.UrtItemCandidateDecorator
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.UrtMultipleModulesDecorator
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.tweet.TweetCandidateUrtItemBuilder
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.ManualModuleId
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.StaticModuleDisplayTypeBuilder
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.TimelineModuleBuilder
|
||||
import com.twitter.product_mixer.component_library.filter.FeatureFilter
|
||||
import com.twitter.product_mixer.component_library.filter.PredicateFeatureFilter
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSource
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.CandidateDecorator
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.BaseCandidateFeatureHydrator
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.gate.Gate
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidateFeatureTransformer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidatePipelineQueryTransformer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidatePipelineResultsTransformer
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidatePipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.EntryNamespace
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.timeline_module.VerticalConversation
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.CandidatePipelineConfig
|
||||
import com.twitter.timelines.configapi.decider.DeciderParam
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ForYouScoredTweetsCandidatePipelineConfig @Inject() (
|
||||
scoredTweetsProductCandidateSource: ScoredTweetsProductCandidateSource,
|
||||
focalTweetFeatureHydrator: FocalTweetFeatureHydrator,
|
||||
namesFeatureHydrator: NamesFeatureHydrator,
|
||||
tweetypieFeatureHydrator: TweetypieFeatureHydrator,
|
||||
invalidSubscriptionTweetFilter: InvalidSubscriptionTweetFilter,
|
||||
homeFeedbackActionInfoBuilder: HomeFeedbackActionInfoBuilder,
|
||||
homeTweetSocialContextBuilder: HomeTweetSocialContextBuilder)
|
||||
extends CandidatePipelineConfig[
|
||||
ForYouQuery,
|
||||
ForYouQuery,
|
||||
ScoredTweetWithConversationMetadata,
|
||||
TweetCandidate
|
||||
] {
|
||||
|
||||
override val identifier: CandidatePipelineIdentifier =
|
||||
CandidatePipelineIdentifier("ForYouScoredTweets")
|
||||
|
||||
private val TweetypieHydratedFilterId = "TweetypieHydrated"
|
||||
private val QuotedTweetDroppedFilterId = "QuotedTweetDropped"
|
||||
private val OutOfNetworkNSFWFilterId = "OutOfNetworkNSFW"
|
||||
private val ConversationModuleNamespace = EntryNamespace("home-conversation")
|
||||
|
||||
override val gates: Seq[Gate[ForYouQuery]] = Seq(SupportedLanguagesGate)
|
||||
|
||||
override val candidateSource: CandidateSource[ForYouQuery, ScoredTweetWithConversationMetadata] =
|
||||
scoredTweetsProductCandidateSource
|
||||
|
||||
override val enabledDeciderParam: Option[DeciderParam[Boolean]] =
|
||||
Some(EnableScoredTweetsCandidatePipelineParam)
|
||||
|
||||
override val queryTransformer: CandidatePipelineQueryTransformer[ForYouQuery, ForYouQuery] =
|
||||
identity
|
||||
|
||||
override val featuresFromCandidateSourceTransformers: Seq[
|
||||
CandidateFeatureTransformer[ScoredTweetWithConversationMetadata]
|
||||
] = Seq(ForYouScoredTweetsResponseFeatureTransformer)
|
||||
|
||||
override val resultTransformer: CandidatePipelineResultsTransformer[
|
||||
ScoredTweetWithConversationMetadata,
|
||||
TweetCandidate
|
||||
] = { sourceResults => TweetCandidate(sourceResults.tweetId) }
|
||||
|
||||
override val preFilterFeatureHydrationPhase1: Seq[
|
||||
BaseCandidateFeatureHydrator[ForYouQuery, TweetCandidate, _]
|
||||
] = Seq(InNetworkFeatureHydrator, namesFeatureHydrator, tweetypieFeatureHydrator)
|
||||
|
||||
override val filters: Seq[Filter[ForYouQuery, TweetCandidate]] = Seq(
|
||||
FeatureFilter.fromFeature(FilterIdentifier(TweetypieHydratedFilterId), IsHydratedFeature),
|
||||
PredicateFeatureFilter.fromPredicate(
|
||||
FilterIdentifier(QuotedTweetDroppedFilterId),
|
||||
shouldKeepCandidate = { features => !features.getOrElse(QuotedTweetDroppedFeature, false) }
|
||||
),
|
||||
PredicateFeatureFilter.fromPredicate(
|
||||
FilterIdentifier(OutOfNetworkNSFWFilterId),
|
||||
shouldKeepCandidate = { features =>
|
||||
features.getOrElse(InNetworkFeature, false) ||
|
||||
!features.getOrElse(IsNsfwFeature, false)
|
||||
}
|
||||
),
|
||||
SocialContextFilter,
|
||||
invalidSubscriptionTweetFilter,
|
||||
InvalidConversationModuleFilter
|
||||
)
|
||||
|
||||
override val postFilterFeatureHydration: Seq[
|
||||
BaseCandidateFeatureHydrator[ForYouQuery, TweetCandidate, _]
|
||||
] = Seq(focalTweetFeatureHydrator)
|
||||
|
||||
override val decorator: Option[CandidateDecorator[ForYouQuery, TweetCandidate]] = {
|
||||
val clientEventInfoBuilder = HomeClientEventInfoBuilder()
|
||||
|
||||
val tweetItemBuilder = TweetCandidateUrtItemBuilder(
|
||||
clientEventInfoBuilder = clientEventInfoBuilder,
|
||||
socialContextBuilder = Some(homeTweetSocialContextBuilder),
|
||||
timelinesScoreInfoBuilder = Some(HomeTimelinesScoreInfoBuilder),
|
||||
feedbackActionInfoBuilder = Some(homeFeedbackActionInfoBuilder)
|
||||
)
|
||||
|
||||
val tweetDecorator = UrtItemCandidateDecorator(tweetItemBuilder)
|
||||
|
||||
val moduleBuilder = TimelineModuleBuilder(
|
||||
entryNamespace = ConversationModuleNamespace,
|
||||
clientEventInfoBuilder = clientEventInfoBuilder,
|
||||
moduleIdGeneration = ManualModuleId(0L),
|
||||
displayTypeBuilder = StaticModuleDisplayTypeBuilder(VerticalConversation),
|
||||
metadataBuilder = Some(HomeConversationModuleMetadataBuilder())
|
||||
)
|
||||
|
||||
Some(
|
||||
UrtMultipleModulesDecorator(
|
||||
urtItemCandidateDecorator = tweetDecorator,
|
||||
moduleBuilder = moduleBuilder,
|
||||
groupByKey = (_, _, candidateFeatures) =>
|
||||
candidateFeatures.getOrElse(ConversationModuleFocalTweetIdFeature, None)
|
||||
))
|
||||
}
|
||||
|
||||
override val alerts = Seq(
|
||||
HomeMixerAlertConfig.BusinessHours.defaultSuccessRateAlert(),
|
||||
HomeMixerAlertConfig.BusinessHours.defaultEmptyResponseRateAlert(10, 20)
|
||||
)
|
||||
}
|
Binary file not shown.
@ -1,376 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.clientapp.{thriftscala => ca}
|
||||
import com.twitter.goldfinch.api.AdsInjectionSurfaceAreas
|
||||
import com.twitter.home_mixer.candidate_pipeline.EditedTweetsCandidatePipelineConfig
|
||||
import com.twitter.home_mixer.candidate_pipeline.NewTweetsPillCandidatePipelineConfig
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.AddEntriesWithReplaceAndShowAlertAndCoverInstructionBuilder
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator._
|
||||
import com.twitter.home_mixer.functional_component.selector.DebunchCandidates
|
||||
import com.twitter.home_mixer.functional_component.selector.UpdateConversationModuleId
|
||||
import com.twitter.home_mixer.functional_component.selector.UpdateHomeClientEventDetails
|
||||
import com.twitter.home_mixer.functional_component.selector.UpdateNewTweetsPillDecoration
|
||||
import com.twitter.home_mixer.functional_component.side_effect._
|
||||
import com.twitter.home_mixer.model.ClearCacheIncludeInstruction
|
||||
import com.twitter.home_mixer.model.HomeFeatures.InNetworkFeature
|
||||
import com.twitter.home_mixer.param.HomeGlobalParams.MaxNumberReplaceInstructionsParam
|
||||
import com.twitter.home_mixer.param.HomeMixerFlagName.ScribeClientEventsFlag
|
||||
import com.twitter.home_mixer.product.following.model.HomeMixerExternalStrings
|
||||
import com.twitter.home_mixer.product.for_you.feature_hydrator.TimelineServiceTweetsQueryFeatureHydrator
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.ClearCacheOnPtr
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.EnableFlipInjectionModuleCandidatePipelineParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.FlipInlineInjectionModulePosition
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.ServerMaxResultsParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.TweetPreviewsPositionParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.WhoToFollowPositionParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.WhoToSubscribePositionParam
|
||||
import com.twitter.home_mixer.product.for_you.side_effect.ServedCandidateFeatureKeysKafkaSideEffectBuilder
|
||||
import com.twitter.home_mixer.product.for_you.side_effect.ServedCandidateKeysKafkaSideEffectBuilder
|
||||
import com.twitter.home_mixer.product.for_you.side_effect.ServedStatsSideEffect
|
||||
import com.twitter.home_mixer.util.CandidatesUtil
|
||||
import com.twitter.inject.annotations.Flag
|
||||
import com.twitter.logpipeline.client.common.EventPublisher
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.async.AsyncQueryFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.social_graph.PreviewCreatorsQueryFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.social_graph.SGSFollowedUsersQueryFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.flexible_injection_pipeline.FlipPromptCandidatePipelineConfigBuilder
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.who_to_follow_module.WhoToFollowCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.who_to_subscribe_module.WhoToSubscribeCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.UrtDomainMarshaller
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ClearCacheInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.OrderedBottomCursorBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.OrderedTopCursorBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ReplaceAllEntries
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ReplaceEntryInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ShowAlertInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ShowCoverInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.StaticTimelineScribeConfigBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.UrtMetadataBuilder
|
||||
import com.twitter.product_mixer.component_library.selector.DropMaxCandidates
|
||||
import com.twitter.product_mixer.component_library.selector.DropMaxModuleItemCandidates
|
||||
import com.twitter.product_mixer.component_library.selector.DropModuleTooFewModuleItemResults
|
||||
import com.twitter.product_mixer.component_library.selector.InsertAppendResults
|
||||
import com.twitter.product_mixer.component_library.selector.InsertFixedPositionResults
|
||||
import com.twitter.product_mixer.component_library.selector.SelectConditionally
|
||||
import com.twitter.product_mixer.component_library.selector.UpdateSortCandidates
|
||||
import com.twitter.product_mixer.component_library.selector.UpdateSortModuleItemCandidates
|
||||
import com.twitter.product_mixer.component_library.selector.ads.AdsInjector
|
||||
import com.twitter.product_mixer.component_library.selector.ads.InsertAdResults
|
||||
import com.twitter.product_mixer.core.functional_component.common.SpecificPipeline
|
||||
import com.twitter.product_mixer.core.functional_component.common.SpecificPipelines
|
||||
import com.twitter.product_mixer.core.functional_component.configapi.StaticParam
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.functional_component.marshaller.TransportMarshaller
|
||||
import com.twitter.product_mixer.core.functional_component.marshaller.response.urt.UrtTransportMarshaller
|
||||
import com.twitter.product_mixer.core.functional_component.premarshaller.DomainMarshaller
|
||||
import com.twitter.product_mixer.core.functional_component.selector.Selector
|
||||
import com.twitter.product_mixer.core.functional_component.side_effect.PipelineResultSideEffect
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidatePipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.MixerPipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.presentation.ItemCandidateWithDetails
|
||||
import com.twitter.product_mixer.core.model.common.presentation.ModuleCandidateWithDetails
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.Timeline
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineModule
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineScribeConfig
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.tweet.TweetItem
|
||||
import com.twitter.product_mixer.core.pipeline.FailOpenPolicy
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.CandidatePipelineConfig
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.DependentCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.core.pipeline.mixer.MixerPipelineConfig
|
||||
import com.twitter.product_mixer.core.product.guice.scope.ProductScoped
|
||||
import com.twitter.stringcenter.client.StringCenter
|
||||
import com.twitter.timelines.render.{thriftscala => urt}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ForYouScoredTweetsMixerPipelineConfig @Inject() (
|
||||
forYouAdsDependentCandidatePipelineBuilder: ForYouAdsDependentCandidatePipelineBuilder,
|
||||
forYouConversationServiceCandidatePipelineConfig: ForYouConversationServiceCandidatePipelineConfig,
|
||||
forYouPushToHomeTweetCandidatePipelineConfig: ForYouPushToHomeTweetCandidatePipelineConfig,
|
||||
forYouScoredTweetsCandidatePipelineConfig: ForYouScoredTweetsCandidatePipelineConfig,
|
||||
forYouWhoToFollowCandidatePipelineConfigBuilder: ForYouWhoToFollowCandidatePipelineConfigBuilder,
|
||||
forYouWhoToSubscribeCandidatePipelineConfigBuilder: ForYouWhoToSubscribeCandidatePipelineConfigBuilder,
|
||||
flipPromptCandidatePipelineConfigBuilder: FlipPromptCandidatePipelineConfigBuilder,
|
||||
editedTweetsCandidatePipelineConfig: EditedTweetsCandidatePipelineConfig,
|
||||
newTweetsPillCandidatePipelineConfig: NewTweetsPillCandidatePipelineConfig[ForYouQuery],
|
||||
forYouTweetPreviewsCandidatePipelineConfig: ForYouTweetPreviewsCandidatePipelineConfig,
|
||||
dismissInfoQueryFeatureHydrator: DismissInfoQueryFeatureHydrator,
|
||||
gizmoduckUserQueryFeatureHydrator: GizmoduckUserQueryFeatureHydrator,
|
||||
persistenceStoreQueryFeatureHydrator: PersistenceStoreQueryFeatureHydrator,
|
||||
requestQueryFeatureHydrator: RequestQueryFeatureHydrator[ForYouQuery],
|
||||
timelineServiceTweetsQueryFeatureHydrator: TimelineServiceTweetsQueryFeatureHydrator,
|
||||
previewCreatorsQueryFeatureHydrator: PreviewCreatorsQueryFeatureHydrator,
|
||||
sgsFollowedUsersQueryFeatureHydrator: SGSFollowedUsersQueryFeatureHydrator,
|
||||
adsInjector: AdsInjector,
|
||||
servedCandidateKeysKafkaSideEffectBuilder: ServedCandidateKeysKafkaSideEffectBuilder,
|
||||
servedCandidateFeatureKeysKafkaSideEffectBuilder: ServedCandidateFeatureKeysKafkaSideEffectBuilder,
|
||||
updateTimelinesPersistenceStoreSideEffect: UpdateTimelinesPersistenceStoreSideEffect,
|
||||
truncateTimelinesPersistenceStoreSideEffect: TruncateTimelinesPersistenceStoreSideEffect,
|
||||
homeScribeServedCandidatesSideEffect: HomeScribeServedCandidatesSideEffect,
|
||||
servedStatsSideEffect: ServedStatsSideEffect,
|
||||
clientEventsScribeEventPublisher: EventPublisher[ca.LogEvent],
|
||||
externalStrings: HomeMixerExternalStrings,
|
||||
@ProductScoped stringCenterProvider: Provider[StringCenter],
|
||||
urtTransportMarshaller: UrtTransportMarshaller,
|
||||
@Flag(ScribeClientEventsFlag) enableScribeClientEvents: Boolean)
|
||||
extends MixerPipelineConfig[ForYouQuery, Timeline, urt.TimelineResponse] {
|
||||
|
||||
override val identifier: MixerPipelineIdentifier = MixerPipelineIdentifier("ForYouScoredTweets")
|
||||
|
||||
private val MaxConsecutiveOutOfNetworkCandidates = 2
|
||||
|
||||
private val PushToHomeTweetPosition = 0
|
||||
|
||||
private val dependentCandidatesStep = MixerPipelineConfig.dependentCandidatePipelinesStep
|
||||
|
||||
override val fetchQueryFeatures: Seq[QueryFeatureHydrator[ForYouQuery]] = Seq(
|
||||
requestQueryFeatureHydrator,
|
||||
persistenceStoreQueryFeatureHydrator,
|
||||
timelineServiceTweetsQueryFeatureHydrator,
|
||||
previewCreatorsQueryFeatureHydrator,
|
||||
sgsFollowedUsersQueryFeatureHydrator,
|
||||
AsyncQueryFeatureHydrator(dependentCandidatesStep, dismissInfoQueryFeatureHydrator),
|
||||
AsyncQueryFeatureHydrator(dependentCandidatesStep, gizmoduckUserQueryFeatureHydrator),
|
||||
)
|
||||
|
||||
private val scoredTweetsCandidatePipelineScope =
|
||||
SpecificPipeline(forYouScoredTweetsCandidatePipelineConfig.identifier)
|
||||
|
||||
private val forYouAdsCandidatePipelineConfig = forYouAdsDependentCandidatePipelineBuilder
|
||||
.build(scoredTweetsCandidatePipelineScope)
|
||||
|
||||
private val forYouWhoToFollowCandidatePipelineConfig =
|
||||
forYouWhoToFollowCandidatePipelineConfigBuilder.build()
|
||||
|
||||
private val forYouWhoToSubscribeCandidatePipelineConfig =
|
||||
forYouWhoToSubscribeCandidatePipelineConfigBuilder.build()
|
||||
|
||||
private val flipPromptCandidatePipelineConfig =
|
||||
flipPromptCandidatePipelineConfigBuilder.build[ForYouQuery](
|
||||
supportedClientParam = Some(EnableFlipInjectionModuleCandidatePipelineParam)
|
||||
)
|
||||
|
||||
override val candidatePipelines: Seq[CandidatePipelineConfig[ForYouQuery, _, _, _]] = Seq(
|
||||
forYouScoredTweetsCandidatePipelineConfig,
|
||||
forYouPushToHomeTweetCandidatePipelineConfig,
|
||||
forYouWhoToFollowCandidatePipelineConfig,
|
||||
forYouWhoToSubscribeCandidatePipelineConfig,
|
||||
forYouTweetPreviewsCandidatePipelineConfig,
|
||||
flipPromptCandidatePipelineConfig
|
||||
)
|
||||
|
||||
override val dependentCandidatePipelines: Seq[
|
||||
DependentCandidatePipelineConfig[ForYouQuery, _, _, _]
|
||||
] = Seq(
|
||||
forYouAdsCandidatePipelineConfig,
|
||||
forYouConversationServiceCandidatePipelineConfig,
|
||||
editedTweetsCandidatePipelineConfig,
|
||||
newTweetsPillCandidatePipelineConfig
|
||||
)
|
||||
|
||||
override val failOpenPolicies: Map[CandidatePipelineIdentifier, FailOpenPolicy] = Map(
|
||||
forYouScoredTweetsCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
forYouAdsCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
forYouWhoToFollowCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
forYouWhoToSubscribeCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
forYouTweetPreviewsCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
flipPromptCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
editedTweetsCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
newTweetsPillCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
)
|
||||
|
||||
override val resultSelectors: Seq[Selector[ForYouQuery]] = Seq(
|
||||
UpdateSortCandidates(
|
||||
ordering = CandidatesUtil.reverseChronTweetsOrdering,
|
||||
candidatePipeline = forYouConversationServiceCandidatePipelineConfig.identifier
|
||||
),
|
||||
UpdateSortCandidates(
|
||||
ordering = CandidatesUtil.scoreOrdering,
|
||||
candidatePipeline = forYouScoredTweetsCandidatePipelineConfig.identifier
|
||||
),
|
||||
UpdateSortModuleItemCandidates(
|
||||
candidatePipeline = forYouScoredTweetsCandidatePipelineConfig.identifier,
|
||||
ordering = CandidatesUtil.conversationModuleTweetsOrdering
|
||||
),
|
||||
DebunchCandidates(
|
||||
pipelineScope = SpecificPipeline(forYouScoredTweetsCandidatePipelineConfig.identifier),
|
||||
mustDebunch = {
|
||||
case item: ItemCandidateWithDetails =>
|
||||
!item.features.getOrElse(InNetworkFeature, false)
|
||||
case module: ModuleCandidateWithDetails =>
|
||||
!module.candidates.last.features.getOrElse(InNetworkFeature, false)
|
||||
},
|
||||
maxBunchSize = MaxConsecutiveOutOfNetworkCandidates
|
||||
),
|
||||
UpdateConversationModuleId(
|
||||
pipelineScope = SpecificPipeline(forYouScoredTweetsCandidatePipelineConfig.identifier)
|
||||
),
|
||||
DropMaxCandidates(
|
||||
candidatePipeline = forYouConversationServiceCandidatePipelineConfig.identifier,
|
||||
maxSelectionsParam = ServerMaxResultsParam
|
||||
),
|
||||
DropMaxCandidates(
|
||||
candidatePipeline = forYouScoredTweetsCandidatePipelineConfig.identifier,
|
||||
maxSelectionsParam = ServerMaxResultsParam
|
||||
),
|
||||
DropMaxCandidates(
|
||||
candidatePipeline = editedTweetsCandidatePipelineConfig.identifier,
|
||||
maxSelectionsParam = MaxNumberReplaceInstructionsParam
|
||||
),
|
||||
DropMaxModuleItemCandidates(
|
||||
candidatePipeline = forYouWhoToFollowCandidatePipelineConfig.identifier,
|
||||
maxModuleItemsParam = StaticParam(WhoToFollowCandidatePipelineConfig.MaxCandidatesSize)
|
||||
),
|
||||
DropMaxModuleItemCandidates(
|
||||
candidatePipeline = forYouWhoToSubscribeCandidatePipelineConfig.identifier,
|
||||
maxModuleItemsParam = StaticParam(WhoToSubscribeCandidatePipelineConfig.MaxCandidatesSize)
|
||||
),
|
||||
// The Conversation Service pipeline will only run if the Scored Tweets pipeline returned nothing
|
||||
InsertAppendResults(
|
||||
candidatePipeline = forYouConversationServiceCandidatePipelineConfig.identifier
|
||||
),
|
||||
InsertAppendResults(
|
||||
candidatePipeline = forYouScoredTweetsCandidatePipelineConfig.identifier
|
||||
),
|
||||
InsertFixedPositionResults(
|
||||
candidatePipeline = forYouTweetPreviewsCandidatePipelineConfig.identifier,
|
||||
positionParam = TweetPreviewsPositionParam
|
||||
),
|
||||
InsertFixedPositionResults(
|
||||
candidatePipeline = forYouWhoToFollowCandidatePipelineConfig.identifier,
|
||||
positionParam = WhoToFollowPositionParam
|
||||
),
|
||||
InsertFixedPositionResults(
|
||||
candidatePipeline = forYouWhoToSubscribeCandidatePipelineConfig.identifier,
|
||||
positionParam = WhoToSubscribePositionParam
|
||||
),
|
||||
InsertFixedPositionResults(
|
||||
candidatePipeline = flipPromptCandidatePipelineConfig.identifier,
|
||||
positionParam = FlipInlineInjectionModulePosition
|
||||
),
|
||||
// Insert Push To Home Tweet at top of Timeline
|
||||
InsertFixedPositionResults(
|
||||
candidatePipeline = forYouPushToHomeTweetCandidatePipelineConfig.identifier,
|
||||
positionParam = StaticParam(PushToHomeTweetPosition)
|
||||
),
|
||||
InsertAdResults(
|
||||
surfaceAreaName = AdsInjectionSurfaceAreas.HomeTimeline,
|
||||
adsInjector = adsInjector.forSurfaceArea(AdsInjectionSurfaceAreas.HomeTimeline),
|
||||
adsCandidatePipeline = forYouAdsCandidatePipelineConfig.identifier
|
||||
),
|
||||
// This selector must come after the tweets are inserted into the results
|
||||
DropModuleTooFewModuleItemResults(
|
||||
candidatePipeline = forYouWhoToFollowCandidatePipelineConfig.identifier,
|
||||
minModuleItemsParam = StaticParam(WhoToFollowCandidatePipelineConfig.MinCandidatesSize)
|
||||
),
|
||||
DropModuleTooFewModuleItemResults(
|
||||
candidatePipeline = forYouWhoToSubscribeCandidatePipelineConfig.identifier,
|
||||
minModuleItemsParam = StaticParam(WhoToSubscribeCandidatePipelineConfig.MinCandidatesSize)
|
||||
),
|
||||
UpdateNewTweetsPillDecoration(
|
||||
pipelineScope = SpecificPipelines(
|
||||
forYouConversationServiceCandidatePipelineConfig.identifier,
|
||||
forYouScoredTweetsCandidatePipelineConfig.identifier,
|
||||
newTweetsPillCandidatePipelineConfig.identifier
|
||||
),
|
||||
stringCenter = stringCenterProvider.get(),
|
||||
seeNewTweetsString = externalStrings.seeNewTweetsString,
|
||||
tweetedString = externalStrings.tweetedString
|
||||
),
|
||||
InsertAppendResults(candidatePipeline = editedTweetsCandidatePipelineConfig.identifier),
|
||||
SelectConditionally(
|
||||
selector =
|
||||
InsertAppendResults(candidatePipeline = newTweetsPillCandidatePipelineConfig.identifier),
|
||||
includeSelector = (_, _, results) => CandidatesUtil.containsType[TweetCandidate](results)
|
||||
),
|
||||
UpdateHomeClientEventDetails(
|
||||
candidatePipelines = Set(
|
||||
forYouConversationServiceCandidatePipelineConfig.identifier,
|
||||
forYouScoredTweetsCandidatePipelineConfig.identifier
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
private val servedCandidateKeysKafkaSideEffect =
|
||||
servedCandidateKeysKafkaSideEffectBuilder.build(
|
||||
Set(forYouScoredTweetsCandidatePipelineConfig.identifier))
|
||||
|
||||
private val servedCandidateFeatureKeysKafkaSideEffect =
|
||||
servedCandidateFeatureKeysKafkaSideEffectBuilder.build(
|
||||
Set(forYouScoredTweetsCandidatePipelineConfig.identifier))
|
||||
|
||||
private val homeScribeClientEventSideEffect = HomeScribeClientEventSideEffect(
|
||||
enableScribeClientEvents = enableScribeClientEvents,
|
||||
logPipelinePublisher = clientEventsScribeEventPublisher,
|
||||
injectedTweetsCandidatePipelineIdentifiers = Seq(
|
||||
forYouScoredTweetsCandidatePipelineConfig.identifier,
|
||||
forYouConversationServiceCandidatePipelineConfig.identifier
|
||||
),
|
||||
adsCandidatePipelineIdentifier = Some(forYouAdsCandidatePipelineConfig.identifier),
|
||||
whoToFollowCandidatePipelineIdentifier = Some(
|
||||
forYouWhoToFollowCandidatePipelineConfig.identifier
|
||||
),
|
||||
whoToSubscribeCandidatePipelineIdentifier =
|
||||
Some(forYouWhoToSubscribeCandidatePipelineConfig.identifier)
|
||||
)
|
||||
|
||||
override val resultSideEffects: Seq[PipelineResultSideEffect[ForYouQuery, Timeline]] = Seq(
|
||||
servedCandidateKeysKafkaSideEffect,
|
||||
servedCandidateFeatureKeysKafkaSideEffect,
|
||||
updateTimelinesPersistenceStoreSideEffect,
|
||||
truncateTimelinesPersistenceStoreSideEffect,
|
||||
homeScribeClientEventSideEffect,
|
||||
homeScribeServedCandidatesSideEffect,
|
||||
servedStatsSideEffect
|
||||
)
|
||||
|
||||
override val domainMarshaller: DomainMarshaller[ForYouQuery, Timeline] = {
|
||||
val instructionBuilders = Seq(
|
||||
ClearCacheInstructionBuilder(
|
||||
ClearCacheIncludeInstruction(
|
||||
ClearCacheOnPtr.EnableParam,
|
||||
ClearCacheOnPtr.MinEntriesParam,
|
||||
)
|
||||
),
|
||||
ReplaceEntryInstructionBuilder(ReplaceAllEntries),
|
||||
// excludes alert, cover, and replace candidates
|
||||
AddEntriesWithReplaceAndShowAlertAndCoverInstructionBuilder(),
|
||||
ShowAlertInstructionBuilder(),
|
||||
ShowCoverInstructionBuilder(),
|
||||
)
|
||||
|
||||
val idSelector: PartialFunction[UniversalNoun[_], Long] = {
|
||||
// exclude ads while determining tweet cursor values
|
||||
case item: TweetItem if item.promotedMetadata.isEmpty => item.id
|
||||
case module: TimelineModule
|
||||
if module.items.headOption.exists(_.item.isInstanceOf[TweetItem]) =>
|
||||
module.items.last.item match { case item: TweetItem => item.id }
|
||||
}
|
||||
val topCursorBuilder = OrderedTopCursorBuilder(idSelector)
|
||||
val bottomCursorBuilder = OrderedBottomCursorBuilder(idSelector)
|
||||
|
||||
val metadataBuilder = UrtMetadataBuilder(
|
||||
title = None,
|
||||
scribeConfigBuilder = Some(
|
||||
StaticTimelineScribeConfigBuilder(
|
||||
TimelineScribeConfig(
|
||||
page = Some("for_you_scored_tweets"),
|
||||
section = None,
|
||||
entityToken = None)))
|
||||
)
|
||||
|
||||
UrtDomainMarshaller(
|
||||
instructionBuilders = instructionBuilders,
|
||||
metadataBuilder = Some(metadataBuilder),
|
||||
cursorBuilders = Seq(topCursorBuilder, bottomCursorBuilder)
|
||||
)
|
||||
}
|
||||
|
||||
override val transportMarshaller: TransportMarshaller[Timeline, urt.TimelineResponse] =
|
||||
urtTransportMarshaller
|
||||
}
|
Binary file not shown.
@ -1,103 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.home_mixer.model.HomeFeatures._
|
||||
import com.twitter.home_mixer.product.for_you.candidate_source.ScoredTweetWithConversationMetadata
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidateFeatureTransformer
|
||||
import com.twitter.product_mixer.core.model.common.identifier.TransformerIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.BasicTopicContextFunctionalityType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.RecWithEducationTopicContextFunctionalityType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.RecommendationTopicContextFunctionalityType
|
||||
import com.twitter.timelines.render.{thriftscala => tl}
|
||||
import com.twitter.timelineservice.suggests.{thriftscala => tls}
|
||||
|
||||
object ForYouScoredTweetsResponseFeatureTransformer
|
||||
extends CandidateFeatureTransformer[ScoredTweetWithConversationMetadata] {
|
||||
|
||||
override val identifier: TransformerIdentifier =
|
||||
TransformerIdentifier("ForYouScoredTweetsResponse")
|
||||
|
||||
override val features: Set[Feature[_, _]] = Set(
|
||||
AncestorsFeature,
|
||||
AuthorIdFeature,
|
||||
AuthorIsBlueVerifiedFeature,
|
||||
AuthorIsCreatorFeature,
|
||||
AuthorIsGoldVerifiedFeature,
|
||||
AuthorIsGrayVerifiedFeature,
|
||||
AuthorIsLegacyVerifiedFeature,
|
||||
ConversationModuleFocalTweetIdFeature,
|
||||
ConversationModuleIdFeature,
|
||||
DirectedAtUserIdFeature,
|
||||
ExclusiveConversationAuthorIdFeature,
|
||||
FullScoringSucceededFeature,
|
||||
FavoritedByUserIdsFeature,
|
||||
FollowedByUserIdsFeature,
|
||||
InNetworkFeature,
|
||||
InReplyToTweetIdFeature,
|
||||
InReplyToUserIdFeature,
|
||||
IsAncestorCandidateFeature,
|
||||
IsReadFromCacheFeature,
|
||||
IsRetweetFeature,
|
||||
PerspectiveFilteredLikedByUserIdsFeature,
|
||||
QuotedTweetIdFeature,
|
||||
QuotedUserIdFeature,
|
||||
SGSValidFollowedByUserIdsFeature,
|
||||
SGSValidLikedByUserIdsFeature,
|
||||
ScoreFeature,
|
||||
SourceTweetIdFeature,
|
||||
SourceUserIdFeature,
|
||||
StreamToKafkaFeature,
|
||||
SuggestTypeFeature,
|
||||
TopicContextFunctionalityTypeFeature,
|
||||
TopicIdSocialContextFeature
|
||||
)
|
||||
|
||||
override def transform(input: ScoredTweetWithConversationMetadata): FeatureMap =
|
||||
FeatureMapBuilder()
|
||||
.add(AncestorsFeature, input.ancestors.getOrElse(Seq.empty))
|
||||
.add(AuthorIdFeature, Some(input.authorId))
|
||||
.add(AuthorIsBlueVerifiedFeature, input.authorIsBlueVerified.getOrElse(false))
|
||||
.add(AuthorIsGoldVerifiedFeature, input.authorIsGoldVerified.getOrElse(false))
|
||||
.add(AuthorIsGrayVerifiedFeature, input.authorIsGrayVerified.getOrElse(false))
|
||||
.add(AuthorIsLegacyVerifiedFeature, input.authorIsLegacyVerified.getOrElse(false))
|
||||
.add(AuthorIsCreatorFeature, input.authorIsCreator.getOrElse(false))
|
||||
.add(ConversationModuleIdFeature, input.conversationId)
|
||||
.add(ConversationModuleFocalTweetIdFeature, input.conversationFocalTweetId)
|
||||
.add(DirectedAtUserIdFeature, input.directedAtUserId)
|
||||
.add(ExclusiveConversationAuthorIdFeature, input.exclusiveConversationAuthorId)
|
||||
.add(SGSValidLikedByUserIdsFeature, input.sgsValidLikedByUserIds.getOrElse(Seq.empty))
|
||||
.add(SGSValidFollowedByUserIdsFeature, input.sgsValidFollowedByUserIds.getOrElse(Seq.empty))
|
||||
.add(FavoritedByUserIdsFeature, input.sgsValidLikedByUserIds.getOrElse(Seq.empty))
|
||||
.add(FollowedByUserIdsFeature, input.sgsValidFollowedByUserIds.getOrElse(Seq.empty))
|
||||
.add(FullScoringSucceededFeature, true)
|
||||
.add(InNetworkFeature, input.inNetwork.getOrElse(true))
|
||||
.add(InReplyToTweetIdFeature, input.inReplyToTweetId)
|
||||
.add(InReplyToUserIdFeature, input.inReplyToUserId)
|
||||
.add(IsAncestorCandidateFeature, input.conversationFocalTweetId.exists(_ != input.tweetId))
|
||||
.add(IsReadFromCacheFeature, input.isReadFromCache.getOrElse(false))
|
||||
.add(IsRetweetFeature, input.sourceTweetId.isDefined)
|
||||
.add(
|
||||
PerspectiveFilteredLikedByUserIdsFeature,
|
||||
input.perspectiveFilteredLikedByUserIds.getOrElse(Seq.empty))
|
||||
.add(QuotedTweetIdFeature, input.quotedTweetId)
|
||||
.add(QuotedUserIdFeature, input.quotedUserId)
|
||||
.add(ScoreFeature, input.score)
|
||||
.add(SourceTweetIdFeature, input.sourceTweetId)
|
||||
.add(SourceUserIdFeature, input.sourceUserId)
|
||||
.add(StreamToKafkaFeature, input.streamToKafka.getOrElse(false))
|
||||
.add(SuggestTypeFeature, input.suggestType.orElse(Some(tls.SuggestType.Undefined)))
|
||||
.add(
|
||||
TopicContextFunctionalityTypeFeature,
|
||||
input.topicFunctionalityType.collect {
|
||||
case tl.TopicContextFunctionalityType.Basic => BasicTopicContextFunctionalityType
|
||||
case tl.TopicContextFunctionalityType.Recommendation =>
|
||||
RecommendationTopicContextFunctionalityType
|
||||
case tl.TopicContextFunctionalityType.RecWithEducation =>
|
||||
RecWithEducationTopicContextFunctionalityType
|
||||
}
|
||||
)
|
||||
.add(TopicIdSocialContextFeature, input.topicId)
|
||||
.build()
|
||||
}
|
Binary file not shown.
@ -1,241 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.home_mixer.functional_component.decorator.builder.HomeClientEventInfoBuilder
|
||||
import com.twitter.home_mixer.functional_component.decorator.builder.HomeConversationModuleMetadataBuilder
|
||||
import com.twitter.home_mixer.functional_component.decorator.builder.HomeTimelinesScoreInfoBuilder
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.HomeFeedbackActionInfoBuilder
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.HomeTweetSocialContextBuilder
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator.InNetworkFeatureHydrator
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator.NamesFeatureHydrator
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator.PerspectiveFilteredSocialContextFeatureHydrator
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator.SGSValidSocialContextFeatureHydrator
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator.TweetypieFeatureHydrator
|
||||
import com.twitter.home_mixer.functional_component.filter.FeedbackFatigueFilter
|
||||
import com.twitter.home_mixer.functional_component.filter.InvalidConversationModuleFilter
|
||||
import com.twitter.home_mixer.functional_component.filter.InvalidSubscriptionTweetFilter
|
||||
import com.twitter.home_mixer.functional_component.filter.RejectTweetFromViewerFilter
|
||||
import com.twitter.home_mixer.functional_component.filter.RetweetDeduplicationFilter
|
||||
import com.twitter.home_mixer.functional_component.scorer.FeedbackFatigueScorer
|
||||
import com.twitter.home_mixer.functional_component.scorer.OONTweetScalingScorer
|
||||
import com.twitter.home_mixer.marshaller.timelines.DeviceContextMarshaller
|
||||
import com.twitter.home_mixer.marshaller.timelines.TimelineServiceCursorMarshaller
|
||||
import com.twitter.home_mixer.model.HomeFeatures.ConversationModuleFocalTweetIdFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.InNetworkFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.IsHydratedFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.IsNsfwFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.QuotedTweetDroppedFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.TimelineServiceTweetsFeature
|
||||
import com.twitter.home_mixer.model.request.DeviceContext
|
||||
import com.twitter.home_mixer.product.for_you.feature_hydrator.FocalTweetFeatureHydrator
|
||||
import com.twitter.home_mixer.product.for_you.filter.SocialContextFilter
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.EnableTimelineScorerCandidatePipelineParam
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig
|
||||
import com.twitter.product_mixer.component_library.candidate_source.timeline_scorer.ScoredTweetCandidateWithFocalTweet
|
||||
import com.twitter.product_mixer.component_library.candidate_source.timeline_scorer.TimelineScorerCandidateSource
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.UrtItemCandidateDecorator
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.UrtMultipleModulesDecorator
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.tweet.TweetCandidateUrtItemBuilder
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.ManualModuleId
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.StaticModuleDisplayTypeBuilder
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.TimelineModuleBuilder
|
||||
import com.twitter.product_mixer.component_library.filter.FeatureFilter
|
||||
import com.twitter.product_mixer.component_library.filter.PredicateFeatureFilter
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.BaseCandidateSource
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.CandidateDecorator
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.BaseCandidateFeatureHydrator
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.scorer.Scorer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidateFeatureTransformer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidatePipelineQueryTransformer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidatePipelineResultsTransformer
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidatePipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.EntryNamespace
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.timeline_module.VerticalConversation
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.CandidatePipelineConfig
|
||||
import com.twitter.timelines.configapi.FSParam
|
||||
import com.twitter.timelines.model.candidate.CandidateTweetSourceId
|
||||
import com.twitter.timelines.service.{thriftscala => tst}
|
||||
import com.twitter.timelinescorer.{thriftscala => t}
|
||||
import com.twitter.timelineservice.{thriftscala => tlst}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Candidate Pipeline Config that fetches tweets from the Timeline Scorer Candidate Source
|
||||
*/
|
||||
@Singleton
|
||||
class ForYouTimelineScorerCandidatePipelineConfig @Inject() (
|
||||
timelineScorerCandidateSource: TimelineScorerCandidateSource,
|
||||
deviceContextMarshaller: DeviceContextMarshaller,
|
||||
tweetypieFeatureHydrator: TweetypieFeatureHydrator,
|
||||
sgsValidSocialContextFeatureHydrator: SGSValidSocialContextFeatureHydrator,
|
||||
perspectiveFilteredSocialContextFeatureHydrator: PerspectiveFilteredSocialContextFeatureHydrator,
|
||||
namesFeatureHydrator: NamesFeatureHydrator,
|
||||
focalTweetFeatureHydrator: FocalTweetFeatureHydrator,
|
||||
invalidSubscriptionTweetFilter: InvalidSubscriptionTweetFilter,
|
||||
homeFeedbackActionInfoBuilder: HomeFeedbackActionInfoBuilder,
|
||||
homeTweetSocialContextBuilder: HomeTweetSocialContextBuilder)
|
||||
extends CandidatePipelineConfig[
|
||||
ForYouQuery,
|
||||
t.ScoredTweetsRequest,
|
||||
ScoredTweetCandidateWithFocalTweet,
|
||||
TweetCandidate
|
||||
] {
|
||||
|
||||
override val identifier: CandidatePipelineIdentifier =
|
||||
CandidatePipelineIdentifier("ForYouTimelineScorerTweets")
|
||||
|
||||
private val TweetypieHydratedFilterId = "TweetypieHydrated"
|
||||
private val QuotedTweetDroppedFilterId = "QuotedTweetDropped"
|
||||
private val OutOfNetworkNSFWFilterId = "OutOfNetworkNSFW"
|
||||
private val ConversationModuleNamespace = EntryNamespace("home-conversation")
|
||||
|
||||
override val supportedClientParam: Option[FSParam[Boolean]] =
|
||||
Some(EnableTimelineScorerCandidatePipelineParam)
|
||||
|
||||
override val candidateSource: BaseCandidateSource[
|
||||
t.ScoredTweetsRequest,
|
||||
ScoredTweetCandidateWithFocalTweet
|
||||
] = timelineScorerCandidateSource
|
||||
|
||||
override val queryTransformer: CandidatePipelineQueryTransformer[
|
||||
ForYouQuery,
|
||||
t.ScoredTweetsRequest
|
||||
] = { query =>
|
||||
val deviceContext = query.deviceContext.getOrElse(DeviceContext.Empty)
|
||||
|
||||
val scoredTweetsRequestContext = t.v1.ScoredTweetsRequestContext(
|
||||
contextualUserId = query.clientContext.userId,
|
||||
timelineId = query.clientContext.userId.map(tlst.TimelineId(tlst.TimelineType.Home, _, None)),
|
||||
deviceContext = Some(deviceContextMarshaller(deviceContext, query.clientContext)),
|
||||
seenTweetIds = query.seenTweetIds,
|
||||
contextualUserContext = Some(tst.ContextualUserContext(query.clientContext.userRoles)),
|
||||
timelineRequestCursor = query.pipelineCursor.flatMap(TimelineServiceCursorMarshaller(_))
|
||||
)
|
||||
|
||||
val candidateTweetSourceIds = Seq(
|
||||
CandidateTweetSourceId.RecycledTweet,
|
||||
CandidateTweetSourceId.OrganicTweet,
|
||||
CandidateTweetSourceId.AncestorsOnlyOrganicTweet,
|
||||
CandidateTweetSourceId.BackfillOrganicTweet,
|
||||
CandidateTweetSourceId.CroonTweet,
|
||||
CandidateTweetSourceId.RecommendedTweet,
|
||||
CandidateTweetSourceId.FrsTweet,
|
||||
CandidateTweetSourceId.ListTweet,
|
||||
CandidateTweetSourceId.RecommendedTrendTweet,
|
||||
CandidateTweetSourceId.PopularTopicTweet
|
||||
)
|
||||
|
||||
val timelineServiceTweets =
|
||||
query.features.map(_.getOrElse(TimelineServiceTweetsFeature, Seq.empty)).getOrElse(Seq.empty)
|
||||
|
||||
val timelineEntries = timelineServiceTweets.map { id =>
|
||||
tlst.TimelineEntry.Tweet(tlst.Tweet(statusId = id, sortIndex = id))
|
||||
}
|
||||
|
||||
t.ScoredTweetsRequest.V1(
|
||||
t.v1.ScoredTweetsRequest(
|
||||
scoredTweetsRequestContext = Some(scoredTweetsRequestContext),
|
||||
candidateTweetSourceIds =
|
||||
Some(candidateTweetSourceIds.flatMap(CandidateTweetSourceId.toThrift)),
|
||||
maxResultsCount = query.requestedMaxResults,
|
||||
organicTimeline = Some(
|
||||
tlst.Timeline(
|
||||
timelineId = tlst.TimelineId(
|
||||
timelineType = tlst.TimelineType.Home,
|
||||
id = query.getRequiredUserId,
|
||||
canonicalTimelineId = None),
|
||||
entries = timelineEntries,
|
||||
modules = tlst.TimelineModules()
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override val featuresFromCandidateSourceTransformers: Seq[
|
||||
CandidateFeatureTransformer[ScoredTweetCandidateWithFocalTweet]
|
||||
] = Seq(ForYouTimelineScorerResponseFeatureTransformer)
|
||||
|
||||
override val resultTransformer: CandidatePipelineResultsTransformer[
|
||||
ScoredTweetCandidateWithFocalTweet,
|
||||
TweetCandidate
|
||||
] = { candidateWithFocalTweetId =>
|
||||
TweetCandidate(id = candidateWithFocalTweetId.candidate.tweetId)
|
||||
}
|
||||
|
||||
override val preFilterFeatureHydrationPhase1: Seq[
|
||||
BaseCandidateFeatureHydrator[ForYouQuery, TweetCandidate, _]
|
||||
] = Seq(
|
||||
namesFeatureHydrator,
|
||||
tweetypieFeatureHydrator,
|
||||
InNetworkFeatureHydrator,
|
||||
sgsValidSocialContextFeatureHydrator,
|
||||
perspectiveFilteredSocialContextFeatureHydrator,
|
||||
)
|
||||
|
||||
override def filters: Seq[Filter[ForYouQuery, TweetCandidate]] = Seq(
|
||||
RetweetDeduplicationFilter,
|
||||
FeatureFilter.fromFeature(FilterIdentifier(TweetypieHydratedFilterId), IsHydratedFeature),
|
||||
PredicateFeatureFilter.fromPredicate(
|
||||
FilterIdentifier(QuotedTweetDroppedFilterId),
|
||||
shouldKeepCandidate = { features => !features.getOrElse(QuotedTweetDroppedFeature, false) }
|
||||
),
|
||||
PredicateFeatureFilter.fromPredicate(
|
||||
FilterIdentifier(OutOfNetworkNSFWFilterId),
|
||||
shouldKeepCandidate = { features =>
|
||||
features.getOrElse(InNetworkFeature, false) ||
|
||||
!features.getOrElse(IsNsfwFeature, false)
|
||||
}
|
||||
),
|
||||
FeedbackFatigueFilter,
|
||||
RejectTweetFromViewerFilter,
|
||||
SocialContextFilter,
|
||||
invalidSubscriptionTweetFilter,
|
||||
InvalidConversationModuleFilter
|
||||
)
|
||||
|
||||
override val postFilterFeatureHydration: Seq[
|
||||
BaseCandidateFeatureHydrator[ForYouQuery, TweetCandidate, _]
|
||||
] = Seq(focalTweetFeatureHydrator)
|
||||
|
||||
override val scorers: Seq[Scorer[ForYouQuery, TweetCandidate]] =
|
||||
Seq(OONTweetScalingScorer, FeedbackFatigueScorer)
|
||||
|
||||
override val decorator: Option[CandidateDecorator[ForYouQuery, TweetCandidate]] = {
|
||||
val clientEventInfoBuilder = HomeClientEventInfoBuilder()
|
||||
|
||||
val tweetItemBuilder = TweetCandidateUrtItemBuilder(
|
||||
clientEventInfoBuilder = clientEventInfoBuilder,
|
||||
socialContextBuilder = Some(homeTweetSocialContextBuilder),
|
||||
timelinesScoreInfoBuilder = Some(HomeTimelinesScoreInfoBuilder),
|
||||
feedbackActionInfoBuilder = Some(homeFeedbackActionInfoBuilder)
|
||||
)
|
||||
|
||||
val tweetDecorator = UrtItemCandidateDecorator(tweetItemBuilder)
|
||||
|
||||
val moduleBuilder = TimelineModuleBuilder(
|
||||
entryNamespace = ConversationModuleNamespace,
|
||||
clientEventInfoBuilder = clientEventInfoBuilder,
|
||||
moduleIdGeneration = ManualModuleId(0L),
|
||||
displayTypeBuilder = StaticModuleDisplayTypeBuilder(VerticalConversation),
|
||||
metadataBuilder = Some(HomeConversationModuleMetadataBuilder())
|
||||
)
|
||||
|
||||
Some(
|
||||
UrtMultipleModulesDecorator(
|
||||
urtItemCandidateDecorator = tweetDecorator,
|
||||
moduleBuilder = moduleBuilder,
|
||||
groupByKey = (_, _, candidateFeatures) =>
|
||||
candidateFeatures.getOrElse(ConversationModuleFocalTweetIdFeature, None)
|
||||
))
|
||||
}
|
||||
|
||||
override val alerts: Seq[Alert] = Seq(
|
||||
HomeMixerAlertConfig.BusinessHours.defaultSuccessRateAlert(),
|
||||
HomeMixerAlertConfig.BusinessHours.defaultEmptyResponseRateAlert(10, 20)
|
||||
)
|
||||
}
|
Binary file not shown.
@ -1,403 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.clientapp.{thriftscala => ca}
|
||||
import com.twitter.goldfinch.api.AdsInjectionSurfaceAreas
|
||||
import com.twitter.home_mixer.candidate_pipeline.EditedTweetsCandidatePipelineConfig
|
||||
import com.twitter.home_mixer.candidate_pipeline.NewTweetsPillCandidatePipelineConfig
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.AddEntriesWithReplaceAndShowAlertAndCoverInstructionBuilder
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator.FeedbackHistoryQueryFeatureHydrator
|
||||
import com.twitter.home_mixer.functional_component.feature_hydrator._
|
||||
import com.twitter.home_mixer.functional_component.selector.DebunchCandidates
|
||||
import com.twitter.home_mixer.functional_component.selector.UpdateConversationModuleId
|
||||
import com.twitter.home_mixer.functional_component.selector.UpdateHomeClientEventDetails
|
||||
import com.twitter.home_mixer.functional_component.selector.UpdateNewTweetsPillDecoration
|
||||
import com.twitter.home_mixer.functional_component.side_effect._
|
||||
import com.twitter.home_mixer.model.ClearCacheIncludeInstruction
|
||||
import com.twitter.home_mixer.model.HomeFeatures.InNetworkFeature
|
||||
import com.twitter.home_mixer.param.HomeGlobalParams.EnableImpressionBloomFilter
|
||||
import com.twitter.home_mixer.param.HomeGlobalParams.MaxNumberReplaceInstructionsParam
|
||||
import com.twitter.home_mixer.param.HomeMixerFlagName.ScribeClientEventsFlag
|
||||
import com.twitter.home_mixer.product.following.model.HomeMixerExternalStrings
|
||||
import com.twitter.home_mixer.product.for_you.feature_hydrator.TimelineServiceTweetsQueryFeatureHydrator
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.ClearCacheOnPtr
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.EnableFlipInjectionModuleCandidatePipelineParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.FlipInlineInjectionModulePosition
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.ServerMaxResultsParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.TweetPreviewsPositionParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.WhoToFollowPositionParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.WhoToSubscribePositionParam
|
||||
import com.twitter.home_mixer.product.for_you.side_effect.ServedCandidateFeatureKeysKafkaSideEffectBuilder
|
||||
import com.twitter.home_mixer.product.for_you.side_effect.ServedCandidateKeysKafkaSideEffectBuilder
|
||||
import com.twitter.home_mixer.product.for_you.side_effect.ServedStatsSideEffect
|
||||
import com.twitter.home_mixer.util.CandidatesUtil
|
||||
import com.twitter.inject.annotations.Flag
|
||||
import com.twitter.logpipeline.client.common.EventPublisher
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.async.AsyncQueryFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.impressed_tweets.ImpressedTweetsQueryFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.param_gated.AsyncParamGatedQueryFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.social_graph.PreviewCreatorsQueryFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.social_graph.SGSFollowedUsersQueryFeatureHydrator
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.flexible_injection_pipeline.FlipPromptCandidatePipelineConfigBuilder
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.who_to_follow_module.WhoToFollowArmCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.who_to_subscribe_module.WhoToSubscribeCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.UrtDomainMarshaller
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ClearCacheInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.OrderedBottomCursorBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.OrderedTopCursorBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ReplaceAllEntries
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ReplaceEntryInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ShowAlertInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.ShowCoverInstructionBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.StaticTimelineScribeConfigBuilder
|
||||
import com.twitter.product_mixer.component_library.premarshaller.urt.builder.UrtMetadataBuilder
|
||||
import com.twitter.product_mixer.component_library.selector.DropMaxCandidates
|
||||
import com.twitter.product_mixer.component_library.selector.DropMaxModuleItemCandidates
|
||||
import com.twitter.product_mixer.component_library.selector.DropModuleTooFewModuleItemResults
|
||||
import com.twitter.product_mixer.component_library.selector.InsertAppendResults
|
||||
import com.twitter.product_mixer.component_library.selector.InsertFixedPositionResults
|
||||
import com.twitter.product_mixer.component_library.selector.SelectConditionally
|
||||
import com.twitter.product_mixer.component_library.selector.UpdateSortCandidates
|
||||
import com.twitter.product_mixer.component_library.selector.UpdateSortModuleItemCandidates
|
||||
import com.twitter.product_mixer.component_library.selector.ads.AdsInjector
|
||||
import com.twitter.product_mixer.component_library.selector.ads.InsertAdResults
|
||||
import com.twitter.product_mixer.core.functional_component.common.SpecificPipeline
|
||||
import com.twitter.product_mixer.core.functional_component.common.SpecificPipelines
|
||||
import com.twitter.product_mixer.core.functional_component.configapi.StaticParam
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.functional_component.marshaller.TransportMarshaller
|
||||
import com.twitter.product_mixer.core.functional_component.marshaller.response.urt.UrtTransportMarshaller
|
||||
import com.twitter.product_mixer.core.functional_component.premarshaller.DomainMarshaller
|
||||
import com.twitter.product_mixer.core.functional_component.selector.Selector
|
||||
import com.twitter.product_mixer.core.functional_component.side_effect.PipelineResultSideEffect
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidatePipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.MixerPipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.presentation.ItemCandidateWithDetails
|
||||
import com.twitter.product_mixer.core.model.common.presentation.ModuleCandidateWithDetails
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.Timeline
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineModule
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineScribeConfig
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.tweet.TweetItem
|
||||
import com.twitter.product_mixer.core.pipeline.FailOpenPolicy
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.CandidatePipelineConfig
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.DependentCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.core.pipeline.mixer.MixerPipelineConfig
|
||||
import com.twitter.product_mixer.core.product.guice.scope.ProductScoped
|
||||
import com.twitter.stringcenter.client.StringCenter
|
||||
import com.twitter.timelines.render.{thriftscala => urt}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ForYouTimelineScorerMixerPipelineConfig @Inject() (
|
||||
forYouTimelineScorerCandidatePipelineConfig: ForYouTimelineScorerCandidatePipelineConfig,
|
||||
forYouPushToHomeTweetCandidatePipelineConfig: ForYouPushToHomeTweetCandidatePipelineConfig,
|
||||
forYouConversationServiceCandidatePipelineConfig: ForYouConversationServiceCandidatePipelineConfig,
|
||||
forYouAdsDependentCandidatePipelineBuilder: ForYouAdsDependentCandidatePipelineBuilder,
|
||||
forYouWhoToFollowCandidatePipelineConfigBuilder: ForYouWhoToFollowCandidatePipelineConfigBuilder,
|
||||
forYouWhoToSubscribeCandidatePipelineConfigBuilder: ForYouWhoToSubscribeCandidatePipelineConfigBuilder,
|
||||
flipPromptCandidatePipelineConfigBuilder: FlipPromptCandidatePipelineConfigBuilder,
|
||||
editedTweetsCandidatePipelineConfig: EditedTweetsCandidatePipelineConfig,
|
||||
newTweetsPillCandidatePipelineConfig: NewTweetsPillCandidatePipelineConfig[ForYouQuery],
|
||||
forYouTweetPreviewsCandidatePipelineConfig: ForYouTweetPreviewsCandidatePipelineConfig,
|
||||
dismissInfoQueryFeatureHydrator: DismissInfoQueryFeatureHydrator,
|
||||
gizmoduckUserQueryFeatureHydrator: GizmoduckUserQueryFeatureHydrator,
|
||||
impressionBloomFilterQueryFeatureHydrator: ImpressionBloomFilterQueryFeatureHydrator[ForYouQuery],
|
||||
manhattanTweetImpressionsQueryFeatureHydrator: TweetImpressionsQueryFeatureHydrator[ForYouQuery],
|
||||
memcacheTweetImpressionsQueryFeatureHydrator: ImpressedTweetsQueryFeatureHydrator,
|
||||
persistenceStoreQueryFeatureHydrator: PersistenceStoreQueryFeatureHydrator,
|
||||
requestQueryFeatureHydrator: RequestQueryFeatureHydrator[ForYouQuery],
|
||||
timelineServiceTweetsQueryFeatureHydrator: TimelineServiceTweetsQueryFeatureHydrator,
|
||||
lastNonPollingTimeQueryFeatureHydrator: LastNonPollingTimeQueryFeatureHydrator,
|
||||
feedbackHistoryQueryFeatureHydrator: FeedbackHistoryQueryFeatureHydrator,
|
||||
previewCreatorsQueryFeatureHydrator: PreviewCreatorsQueryFeatureHydrator,
|
||||
sgsFollowedUsersQueryFeatureHydrator: SGSFollowedUsersQueryFeatureHydrator,
|
||||
adsInjector: AdsInjector,
|
||||
servedCandidateKeysKafkaSideEffectBuilder: ServedCandidateKeysKafkaSideEffectBuilder,
|
||||
servedCandidateFeatureKeysKafkaSideEffectBuilder: ServedCandidateFeatureKeysKafkaSideEffectBuilder,
|
||||
updateLastNonPollingTimeSideEffect: UpdateLastNonPollingTimeSideEffect[ForYouQuery, Timeline],
|
||||
publishClientSentImpressionsEventBusSideEffect: PublishClientSentImpressionsEventBusSideEffect,
|
||||
publishClientSentImpressionsManhattanSideEffect: PublishClientSentImpressionsManhattanSideEffect,
|
||||
publishImpressionBloomFilterSideEffect: PublishImpressionBloomFilterSideEffect,
|
||||
updateTimelinesPersistenceStoreSideEffect: UpdateTimelinesPersistenceStoreSideEffect,
|
||||
truncateTimelinesPersistenceStoreSideEffect: TruncateTimelinesPersistenceStoreSideEffect,
|
||||
homeScribeServedCandidatesSideEffect: HomeScribeServedCandidatesSideEffect,
|
||||
servedStatsSideEffect: ServedStatsSideEffect,
|
||||
clientEventsScribeEventPublisher: EventPublisher[ca.LogEvent],
|
||||
externalStrings: HomeMixerExternalStrings,
|
||||
@ProductScoped stringCenterProvider: Provider[StringCenter],
|
||||
urtTransportMarshaller: UrtTransportMarshaller,
|
||||
@Flag(ScribeClientEventsFlag) enableScribeClientEvents: Boolean)
|
||||
extends MixerPipelineConfig[ForYouQuery, Timeline, urt.TimelineResponse] {
|
||||
|
||||
override val identifier: MixerPipelineIdentifier = MixerPipelineIdentifier("ForYouTimelineScorer")
|
||||
|
||||
private val MaxConsecutiveOutOfNetworkCandidates = 2
|
||||
|
||||
private val PushToHomeTweetPosition = 0
|
||||
|
||||
private val dependentCandidatesStep = MixerPipelineConfig.dependentCandidatePipelinesStep
|
||||
private val resultSelectorsStep = MixerPipelineConfig.resultSelectorsStep
|
||||
|
||||
override def fetchQueryFeatures: Seq[QueryFeatureHydrator[ForYouQuery]] = Seq(
|
||||
requestQueryFeatureHydrator,
|
||||
persistenceStoreQueryFeatureHydrator,
|
||||
timelineServiceTweetsQueryFeatureHydrator,
|
||||
feedbackHistoryQueryFeatureHydrator,
|
||||
previewCreatorsQueryFeatureHydrator,
|
||||
sgsFollowedUsersQueryFeatureHydrator,
|
||||
AsyncQueryFeatureHydrator(dependentCandidatesStep, dismissInfoQueryFeatureHydrator),
|
||||
AsyncQueryFeatureHydrator(dependentCandidatesStep, gizmoduckUserQueryFeatureHydrator),
|
||||
AsyncQueryFeatureHydrator(dependentCandidatesStep, lastNonPollingTimeQueryFeatureHydrator),
|
||||
AsyncParamGatedQueryFeatureHydrator(
|
||||
EnableImpressionBloomFilter,
|
||||
resultSelectorsStep,
|
||||
impressionBloomFilterQueryFeatureHydrator),
|
||||
AsyncQueryFeatureHydrator(resultSelectorsStep, manhattanTweetImpressionsQueryFeatureHydrator),
|
||||
AsyncQueryFeatureHydrator(resultSelectorsStep, memcacheTweetImpressionsQueryFeatureHydrator)
|
||||
)
|
||||
|
||||
private val timelineScorerCandidatePipelineScope =
|
||||
SpecificPipeline(forYouTimelineScorerCandidatePipelineConfig.identifier)
|
||||
|
||||
private val forYouAdsCandidatePipelineConfig = forYouAdsDependentCandidatePipelineBuilder
|
||||
.build(timelineScorerCandidatePipelineScope)
|
||||
|
||||
private val forYouWhoToFollowCandidatePipelineConfig =
|
||||
forYouWhoToFollowCandidatePipelineConfigBuilder.build()
|
||||
|
||||
private val forYouWhoToSubscribeCandidatePipelineConfig =
|
||||
forYouWhoToSubscribeCandidatePipelineConfigBuilder.build()
|
||||
|
||||
private val flipPromptCandidatePipelineConfig =
|
||||
flipPromptCandidatePipelineConfigBuilder.build[ForYouQuery](
|
||||
supportedClientParam = Some(EnableFlipInjectionModuleCandidatePipelineParam)
|
||||
)
|
||||
|
||||
override val candidatePipelines: Seq[CandidatePipelineConfig[ForYouQuery, _, _, _]] = Seq(
|
||||
forYouTimelineScorerCandidatePipelineConfig,
|
||||
forYouPushToHomeTweetCandidatePipelineConfig,
|
||||
forYouWhoToFollowCandidatePipelineConfig,
|
||||
forYouWhoToSubscribeCandidatePipelineConfig,
|
||||
forYouTweetPreviewsCandidatePipelineConfig,
|
||||
flipPromptCandidatePipelineConfig
|
||||
)
|
||||
|
||||
override val dependentCandidatePipelines: Seq[
|
||||
DependentCandidatePipelineConfig[ForYouQuery, _, _, _]
|
||||
] = Seq(
|
||||
forYouAdsCandidatePipelineConfig,
|
||||
forYouConversationServiceCandidatePipelineConfig,
|
||||
editedTweetsCandidatePipelineConfig,
|
||||
newTweetsPillCandidatePipelineConfig
|
||||
)
|
||||
|
||||
override val failOpenPolicies: Map[CandidatePipelineIdentifier, FailOpenPolicy] = Map(
|
||||
forYouTimelineScorerCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
forYouAdsCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
forYouWhoToFollowCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
forYouWhoToSubscribeCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
forYouTweetPreviewsCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
flipPromptCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
editedTweetsCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
newTweetsPillCandidatePipelineConfig.identifier -> FailOpenPolicy.Always,
|
||||
)
|
||||
|
||||
override val resultSelectors: Seq[Selector[ForYouQuery]] = Seq(
|
||||
UpdateSortCandidates(
|
||||
ordering = CandidatesUtil.reverseChronTweetsOrdering,
|
||||
candidatePipeline = forYouConversationServiceCandidatePipelineConfig.identifier
|
||||
),
|
||||
UpdateSortCandidates(
|
||||
ordering = CandidatesUtil.scoreOrdering,
|
||||
candidatePipeline = forYouTimelineScorerCandidatePipelineConfig.identifier
|
||||
),
|
||||
UpdateSortModuleItemCandidates(
|
||||
candidatePipeline = forYouTimelineScorerCandidatePipelineConfig.identifier,
|
||||
ordering = CandidatesUtil.conversationModuleTweetsOrdering
|
||||
),
|
||||
DebunchCandidates(
|
||||
pipelineScope = SpecificPipeline(forYouTimelineScorerCandidatePipelineConfig.identifier),
|
||||
mustDebunch = {
|
||||
case item: ItemCandidateWithDetails =>
|
||||
!item.features.getOrElse(InNetworkFeature, false)
|
||||
case module: ModuleCandidateWithDetails =>
|
||||
!module.candidates.last.features.getOrElse(InNetworkFeature, false)
|
||||
},
|
||||
maxBunchSize = MaxConsecutiveOutOfNetworkCandidates
|
||||
),
|
||||
UpdateConversationModuleId(
|
||||
pipelineScope = SpecificPipeline(forYouTimelineScorerCandidatePipelineConfig.identifier)
|
||||
),
|
||||
DropMaxCandidates(
|
||||
candidatePipeline = forYouConversationServiceCandidatePipelineConfig.identifier,
|
||||
maxSelectionsParam = ServerMaxResultsParam
|
||||
),
|
||||
DropMaxCandidates(
|
||||
candidatePipeline = forYouTimelineScorerCandidatePipelineConfig.identifier,
|
||||
maxSelectionsParam = ServerMaxResultsParam
|
||||
),
|
||||
DropMaxCandidates(
|
||||
candidatePipeline = editedTweetsCandidatePipelineConfig.identifier,
|
||||
maxSelectionsParam = MaxNumberReplaceInstructionsParam
|
||||
),
|
||||
DropMaxModuleItemCandidates(
|
||||
candidatePipeline = forYouWhoToFollowCandidatePipelineConfig.identifier,
|
||||
maxModuleItemsParam = StaticParam(WhoToFollowArmCandidatePipelineConfig.MaxCandidatesSize)
|
||||
),
|
||||
DropModuleTooFewModuleItemResults(
|
||||
candidatePipeline = forYouWhoToSubscribeCandidatePipelineConfig.identifier,
|
||||
minModuleItemsParam = StaticParam(WhoToSubscribeCandidatePipelineConfig.MinCandidatesSize)
|
||||
),
|
||||
DropMaxModuleItemCandidates(
|
||||
candidatePipeline = forYouWhoToSubscribeCandidatePipelineConfig.identifier,
|
||||
maxModuleItemsParam = StaticParam(WhoToSubscribeCandidatePipelineConfig.MaxCandidatesSize)
|
||||
),
|
||||
// Add Conversation Service tweets to results only if the scored pipeline doesn't return any
|
||||
SelectConditionally(
|
||||
selector = InsertAppendResults(
|
||||
candidatePipeline = forYouConversationServiceCandidatePipelineConfig.identifier),
|
||||
includeSelector = (_, candidates, _) =>
|
||||
!candidates.exists(candidate =>
|
||||
forYouTimelineScorerCandidatePipelineConfig.identifier == candidate.source)
|
||||
),
|
||||
InsertAppendResults(candidatePipeline = forYouTimelineScorerCandidatePipelineConfig.identifier),
|
||||
InsertFixedPositionResults(
|
||||
candidatePipeline = forYouTweetPreviewsCandidatePipelineConfig.identifier,
|
||||
positionParam = TweetPreviewsPositionParam
|
||||
),
|
||||
InsertFixedPositionResults(
|
||||
candidatePipeline = forYouWhoToFollowCandidatePipelineConfig.identifier,
|
||||
positionParam = WhoToFollowPositionParam
|
||||
),
|
||||
InsertFixedPositionResults(
|
||||
candidatePipeline = forYouWhoToSubscribeCandidatePipelineConfig.identifier,
|
||||
positionParam = WhoToSubscribePositionParam
|
||||
),
|
||||
InsertFixedPositionResults(
|
||||
candidatePipeline = flipPromptCandidatePipelineConfig.identifier,
|
||||
positionParam = FlipInlineInjectionModulePosition
|
||||
),
|
||||
// Insert Push To Home Tweet at top of Timeline
|
||||
InsertFixedPositionResults(
|
||||
candidatePipeline = forYouPushToHomeTweetCandidatePipelineConfig.identifier,
|
||||
positionParam = StaticParam(PushToHomeTweetPosition)
|
||||
),
|
||||
InsertAdResults(
|
||||
surfaceAreaName = AdsInjectionSurfaceAreas.HomeTimeline,
|
||||
adsInjector = adsInjector.forSurfaceArea(AdsInjectionSurfaceAreas.HomeTimeline),
|
||||
adsCandidatePipeline = forYouAdsCandidatePipelineConfig.identifier
|
||||
),
|
||||
// This selector must come after the tweets are inserted into the results
|
||||
DropModuleTooFewModuleItemResults(
|
||||
candidatePipeline = forYouWhoToFollowCandidatePipelineConfig.identifier,
|
||||
minModuleItemsParam = StaticParam(WhoToFollowArmCandidatePipelineConfig.MinCandidatesSize)
|
||||
),
|
||||
UpdateNewTweetsPillDecoration(
|
||||
pipelineScope = SpecificPipelines(
|
||||
forYouConversationServiceCandidatePipelineConfig.identifier,
|
||||
forYouTimelineScorerCandidatePipelineConfig.identifier,
|
||||
newTweetsPillCandidatePipelineConfig.identifier
|
||||
),
|
||||
stringCenter = stringCenterProvider.get(),
|
||||
seeNewTweetsString = externalStrings.seeNewTweetsString,
|
||||
tweetedString = externalStrings.tweetedString
|
||||
),
|
||||
InsertAppendResults(candidatePipeline = editedTweetsCandidatePipelineConfig.identifier),
|
||||
SelectConditionally(
|
||||
selector =
|
||||
InsertAppendResults(candidatePipeline = newTweetsPillCandidatePipelineConfig.identifier),
|
||||
includeSelector = (_, _, results) => CandidatesUtil.containsType[TweetCandidate](results)
|
||||
),
|
||||
UpdateHomeClientEventDetails(
|
||||
candidatePipelines = Set(
|
||||
forYouConversationServiceCandidatePipelineConfig.identifier,
|
||||
forYouTimelineScorerCandidatePipelineConfig.identifier
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
private val servedCandidateKeysKafkaSideEffect =
|
||||
servedCandidateKeysKafkaSideEffectBuilder.build(
|
||||
Set(forYouTimelineScorerCandidatePipelineConfig.identifier))
|
||||
|
||||
private val servedCandidateFeatureKeysKafkaSideEffect =
|
||||
servedCandidateFeatureKeysKafkaSideEffectBuilder.build(
|
||||
Set(forYouTimelineScorerCandidatePipelineConfig.identifier))
|
||||
|
||||
private val homeScribeClientEventSideEffect = HomeScribeClientEventSideEffect(
|
||||
enableScribeClientEvents = enableScribeClientEvents,
|
||||
logPipelinePublisher = clientEventsScribeEventPublisher,
|
||||
injectedTweetsCandidatePipelineIdentifiers = Seq(
|
||||
forYouTimelineScorerCandidatePipelineConfig.identifier,
|
||||
forYouConversationServiceCandidatePipelineConfig.identifier
|
||||
),
|
||||
adsCandidatePipelineIdentifier = Some(forYouAdsCandidatePipelineConfig.identifier),
|
||||
whoToFollowCandidatePipelineIdentifier =
|
||||
Some(forYouWhoToFollowCandidatePipelineConfig.identifier),
|
||||
whoToSubscribeCandidatePipelineIdentifier =
|
||||
Some(forYouWhoToSubscribeCandidatePipelineConfig.identifier)
|
||||
)
|
||||
|
||||
override val resultSideEffects: Seq[PipelineResultSideEffect[ForYouQuery, Timeline]] = Seq(
|
||||
homeScribeClientEventSideEffect,
|
||||
homeScribeServedCandidatesSideEffect,
|
||||
publishClientSentImpressionsEventBusSideEffect,
|
||||
publishClientSentImpressionsManhattanSideEffect,
|
||||
publishImpressionBloomFilterSideEffect,
|
||||
servedCandidateKeysKafkaSideEffect,
|
||||
servedCandidateFeatureKeysKafkaSideEffect,
|
||||
servedStatsSideEffect,
|
||||
truncateTimelinesPersistenceStoreSideEffect,
|
||||
updateLastNonPollingTimeSideEffect,
|
||||
updateTimelinesPersistenceStoreSideEffect
|
||||
)
|
||||
|
||||
override val domainMarshaller: DomainMarshaller[ForYouQuery, Timeline] = {
|
||||
val instructionBuilders = Seq(
|
||||
ClearCacheInstructionBuilder(
|
||||
ClearCacheIncludeInstruction(
|
||||
ClearCacheOnPtr.EnableParam,
|
||||
ClearCacheOnPtr.MinEntriesParam
|
||||
)
|
||||
),
|
||||
ReplaceEntryInstructionBuilder(ReplaceAllEntries),
|
||||
// excludes alert, cover, and replace candidates
|
||||
AddEntriesWithReplaceAndShowAlertAndCoverInstructionBuilder(),
|
||||
ShowAlertInstructionBuilder(),
|
||||
ShowCoverInstructionBuilder(),
|
||||
)
|
||||
|
||||
val idSelector: PartialFunction[UniversalNoun[_], Long] = {
|
||||
// exclude ads while determining tweet cursor values
|
||||
case item: TweetItem if item.promotedMetadata.isEmpty => item.id
|
||||
case module: TimelineModule
|
||||
if module.items.headOption.exists(_.item.isInstanceOf[TweetItem]) =>
|
||||
module.items.last.item match { case item: TweetItem => item.id }
|
||||
}
|
||||
val topCursorBuilder = OrderedTopCursorBuilder(idSelector)
|
||||
val bottomCursorBuilder = OrderedBottomCursorBuilder(idSelector)
|
||||
|
||||
val metadataBuilder = UrtMetadataBuilder(
|
||||
title = None,
|
||||
scribeConfigBuilder = Some(
|
||||
StaticTimelineScribeConfigBuilder(
|
||||
TimelineScribeConfig(
|
||||
page = Some("for_you_timeline_scorer"),
|
||||
section = None,
|
||||
entityToken = None)))
|
||||
)
|
||||
|
||||
UrtDomainMarshaller(
|
||||
instructionBuilders = instructionBuilders,
|
||||
metadataBuilder = Some(metadataBuilder),
|
||||
cursorBuilders = Seq(topCursorBuilder, bottomCursorBuilder)
|
||||
)
|
||||
}
|
||||
|
||||
override val transportMarshaller: TransportMarshaller[Timeline, urt.TimelineResponse] =
|
||||
urtTransportMarshaller
|
||||
}
|
Binary file not shown.
@ -1,199 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.tweetconvosvc.tweet_ancestor.{thriftscala => ta}
|
||||
import com.twitter.home_mixer.model.HomeFeatures._
|
||||
import com.twitter.mediaservices.commons.tweetmedia.{thriftscala => mt}
|
||||
import com.twitter.product_mixer.component_library.candidate_source.timeline_scorer.ScoredTweetCandidateWithFocalTweet
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidateFeatureTransformer
|
||||
import com.twitter.product_mixer.core.model.common.identifier.TransformerIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.BasicTopicContextFunctionalityType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.RecWithEducationTopicContextFunctionalityType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.RecommendationTopicContextFunctionalityType
|
||||
import com.twitter.search.common.constants.thriftjava.ThriftLanguage
|
||||
import com.twitter.search.common.util.lang.ThriftLanguageUtil
|
||||
import com.twitter.snowflake.id.SnowflakeId
|
||||
import com.twitter.timelinemixer.injection.model.candidate.AudioSpaceMetaData
|
||||
import com.twitter.timelines.conversation_features.{thriftscala => cvt}
|
||||
import com.twitter.timelinescorer.common.scoredtweetcandidate.{thriftscala => stc}
|
||||
import com.twitter.timelineservice.suggests.{thriftscala => tls}
|
||||
|
||||
object ForYouTimelineScorerResponseFeatureTransformer
|
||||
extends CandidateFeatureTransformer[ScoredTweetCandidateWithFocalTweet] {
|
||||
|
||||
override val identifier: TransformerIdentifier =
|
||||
TransformerIdentifier("ForYouTimelineScorerResponse")
|
||||
|
||||
override val features: Set[Feature[_, _]] = Set(
|
||||
AncestorsFeature,
|
||||
AudioSpaceMetaDataFeature,
|
||||
AuthorIdFeature,
|
||||
AuthorIsBlueVerifiedFeature,
|
||||
AuthorIsCreatorFeature,
|
||||
AuthorIsGoldVerifiedFeature,
|
||||
AuthorIsGrayVerifiedFeature,
|
||||
AuthorIsLegacyVerifiedFeature,
|
||||
AuthoredByContextualUserFeature,
|
||||
CandidateSourceIdFeature,
|
||||
ConversationFeature,
|
||||
ConversationModuleFocalTweetIdFeature,
|
||||
ConversationModuleIdFeature,
|
||||
DirectedAtUserIdFeature,
|
||||
EarlybirdFeature,
|
||||
EntityTokenFeature,
|
||||
ExclusiveConversationAuthorIdFeature,
|
||||
FavoritedByUserIdsFeature,
|
||||
FollowedByUserIdsFeature,
|
||||
TopicIdSocialContextFeature,
|
||||
TopicContextFunctionalityTypeFeature,
|
||||
FromInNetworkSourceFeature,
|
||||
FullScoringSucceededFeature,
|
||||
HasDisplayedTextFeature,
|
||||
InNetworkFeature,
|
||||
InReplyToTweetIdFeature,
|
||||
IsAncestorCandidateFeature,
|
||||
IsExtendedReplyFeature,
|
||||
IsRandomTweetFeature,
|
||||
IsReadFromCacheFeature,
|
||||
IsRetweetFeature,
|
||||
IsRetweetedReplyFeature,
|
||||
NonSelfFavoritedByUserIdsFeature,
|
||||
NumImagesFeature,
|
||||
OriginalTweetCreationTimeFromSnowflakeFeature,
|
||||
PredictionRequestIdFeature,
|
||||
QuotedTweetIdFeature,
|
||||
ScoreFeature,
|
||||
SimclustersTweetTopKClustersWithScoresFeature,
|
||||
SourceTweetIdFeature,
|
||||
SourceUserIdFeature,
|
||||
StreamToKafkaFeature,
|
||||
SuggestTypeFeature,
|
||||
TweetLanguageFeature,
|
||||
VideoDurationMsFeature,
|
||||
)
|
||||
|
||||
// Convert language code to ISO 639-3 format
|
||||
private def getLanguageISOFormatByValue(languageCodeValue: Int): String =
|
||||
ThriftLanguageUtil.getLanguageCodeOf(ThriftLanguage.findByValue(languageCodeValue))
|
||||
|
||||
override def transform(
|
||||
candidateWithFocalTweet: ScoredTweetCandidateWithFocalTweet
|
||||
): FeatureMap = {
|
||||
val candidate: stc.v1.ScoredTweetCandidate = candidateWithFocalTweet.candidate
|
||||
val focalTweetId = candidateWithFocalTweet.focalTweetIdOpt
|
||||
|
||||
val originalTweetId = candidate.sourceTweetId.getOrElse(candidate.tweetId)
|
||||
val tweetFeatures = candidate.tweetFeaturesMap.flatMap(_.get(originalTweetId))
|
||||
val earlybirdFeatures = tweetFeatures.flatMap(_.recapFeatures.flatMap(_.tweetFeatures))
|
||||
val directedAtUserIsInFirstDegree =
|
||||
earlybirdFeatures.flatMap(_.directedAtUserIdIsInFirstDegree)
|
||||
val isReply = candidate.inReplyToTweetId.nonEmpty
|
||||
val isRetweet = candidate.isRetweet.getOrElse(false)
|
||||
val isInNetwork = candidate.isInNetwork.getOrElse(true)
|
||||
val conversationFeatures = candidate.conversationFeatures.flatMap {
|
||||
case cvt.ConversationFeatures.V1(candidate) => Some(candidate)
|
||||
case _ => None
|
||||
}
|
||||
val numImages = candidate.mediaMetaData
|
||||
.map(
|
||||
_.count(mediaEntity =>
|
||||
mediaEntity.mediaInfo.exists(_.isInstanceOf[mt.MediaInfo.ImageInfo]) ||
|
||||
mediaEntity.mediaInfo.isEmpty))
|
||||
val hasImage = earlybirdFeatures.exists(_.hasImage)
|
||||
val hasVideo = earlybirdFeatures.exists(_.hasVideo)
|
||||
val hasCard = earlybirdFeatures.exists(_.hasCard)
|
||||
val hasQuote = earlybirdFeatures.exists(_.hasQuote.contains(true))
|
||||
val hasDisplayedText = earlybirdFeatures.exists(_.tweetLength.exists(length => {
|
||||
val numMedia = Seq(hasVideo, (hasImage || hasCard), hasQuote).count(b => b)
|
||||
val tcoLengthsPlusSpaces = 23 * numMedia + (if (numMedia > 0) numMedia - 1 else 0)
|
||||
length > tcoLengthsPlusSpaces
|
||||
}))
|
||||
val suggestType = candidate.overrideSuggestType.orElse(Some(tls.SuggestType.Undefined))
|
||||
|
||||
val topicSocialProofMetadataOpt = candidate.entityData.flatMap(_.topicSocialProofMetadata)
|
||||
val topicIdSocialContextOpt = topicSocialProofMetadataOpt.map(_.topicId)
|
||||
val topicContextFunctionalityTypeOpt =
|
||||
topicSocialProofMetadataOpt.map(_.topicContextFunctionalityType).collect {
|
||||
case stc.v1.TopicContextFunctionalityType.Basic => BasicTopicContextFunctionalityType
|
||||
case stc.v1.TopicContextFunctionalityType.Recommendation =>
|
||||
RecommendationTopicContextFunctionalityType
|
||||
case stc.v1.TopicContextFunctionalityType.RecWithEducation =>
|
||||
RecWithEducationTopicContextFunctionalityType
|
||||
}
|
||||
|
||||
FeatureMapBuilder()
|
||||
.add(
|
||||
AncestorsFeature,
|
||||
candidate.ancestors
|
||||
.getOrElse(Seq.empty)
|
||||
.map(ancestor => ta.TweetAncestor(ancestor.tweetId, ancestor.userId.getOrElse(0L))))
|
||||
.add(
|
||||
AudioSpaceMetaDataFeature,
|
||||
candidate.audioSpaceMetaDatalist.map(_.head).map(AudioSpaceMetaData.fromThrift))
|
||||
.add(AuthorIdFeature, Some(candidate.authorId))
|
||||
.add(AuthorIsBlueVerifiedFeature, candidate.authorIsBlueVerified.getOrElse(false))
|
||||
.add(
|
||||
AuthorIsCreatorFeature,
|
||||
candidate.authorIsCreator.getOrElse(false)
|
||||
)
|
||||
.add(AuthorIsGoldVerifiedFeature, candidate.authorIsGoldVerified.getOrElse(false))
|
||||
.add(AuthorIsGrayVerifiedFeature, candidate.authorIsGrayVerified.getOrElse(false))
|
||||
.add(AuthorIsLegacyVerifiedFeature, candidate.authorIsLegacyVerified.getOrElse(false))
|
||||
.add(
|
||||
AuthoredByContextualUserFeature,
|
||||
candidate.viewerId.contains(candidate.authorId) ||
|
||||
candidate.viewerId.exists(candidate.sourceUserId.contains))
|
||||
.add(CandidateSourceIdFeature, candidate.candidateTweetSourceId)
|
||||
.add(ConversationFeature, conversationFeatures)
|
||||
.add(ConversationModuleIdFeature, candidate.conversationId)
|
||||
.add(ConversationModuleFocalTweetIdFeature, focalTweetId)
|
||||
.add(DirectedAtUserIdFeature, candidate.directedAtUserId)
|
||||
.add(EarlybirdFeature, earlybirdFeatures)
|
||||
// This is temporary, will need to be updated with the encoded string.
|
||||
.add(EntityTokenFeature, Some("test_EntityTokenForYou"))
|
||||
.add(ExclusiveConversationAuthorIdFeature, candidate.exclusiveConversationAuthorId)
|
||||
.add(FavoritedByUserIdsFeature, candidate.favoritedByUserIds.getOrElse(Seq.empty))
|
||||
.add(FollowedByUserIdsFeature, candidate.followedByUserIds.getOrElse(Seq.empty))
|
||||
.add(TopicIdSocialContextFeature, topicIdSocialContextOpt)
|
||||
.add(TopicContextFunctionalityTypeFeature, topicContextFunctionalityTypeOpt)
|
||||
.add(FullScoringSucceededFeature, candidate.fullScoringSucceeded.getOrElse(false))
|
||||
.add(HasDisplayedTextFeature, hasDisplayedText)
|
||||
.add(InNetworkFeature, candidate.isInNetwork.getOrElse(true))
|
||||
.add(InReplyToTweetIdFeature, candidate.inReplyToTweetId)
|
||||
.add(IsAncestorCandidateFeature, candidate.isAncestorCandidate.getOrElse(false))
|
||||
.add(
|
||||
IsExtendedReplyFeature,
|
||||
isInNetwork && isReply && !isRetweet && directedAtUserIsInFirstDegree.contains(false))
|
||||
.add(FromInNetworkSourceFeature, candidate.isInNetwork.getOrElse(true))
|
||||
.add(IsRandomTweetFeature, candidate.isRandomTweet.getOrElse(false))
|
||||
.add(IsReadFromCacheFeature, candidate.isReadFromCache.getOrElse(false))
|
||||
.add(IsRetweetFeature, candidate.isRetweet.getOrElse(false))
|
||||
.add(IsRetweetedReplyFeature, isReply && isRetweet)
|
||||
.add(
|
||||
NonSelfFavoritedByUserIdsFeature,
|
||||
candidate.favoritedByUserIds.getOrElse(Seq.empty).filterNot(_ == candidate.authorId))
|
||||
.add(NumImagesFeature, numImages)
|
||||
.add(
|
||||
OriginalTweetCreationTimeFromSnowflakeFeature,
|
||||
SnowflakeId.timeFromIdOpt(originalTweetId))
|
||||
.add(PredictionRequestIdFeature, candidate.predictionRequestId)
|
||||
.add(ScoreFeature, Some(candidate.score))
|
||||
.add(
|
||||
SimclustersTweetTopKClustersWithScoresFeature,
|
||||
candidate.simclustersTweetTopKClustersWithScores.map(_.toMap).getOrElse(Map.empty))
|
||||
.add(
|
||||
StreamToKafkaFeature,
|
||||
candidate.predictionRequestId.nonEmpty && candidate.fullScoringSucceeded.getOrElse(false))
|
||||
.add(SourceTweetIdFeature, candidate.sourceTweetId)
|
||||
.add(SourceUserIdFeature, candidate.sourceUserId)
|
||||
.add(SuggestTypeFeature, suggestType)
|
||||
.add(QuotedTweetIdFeature, candidate.quotedTweetId)
|
||||
.add(
|
||||
TweetLanguageFeature,
|
||||
earlybirdFeatures.flatMap(_.language.map(_.value)).map(getLanguageISOFormatByValue))
|
||||
.add(VideoDurationMsFeature, earlybirdFeatures.flatMap(_.videoDurationMs))
|
||||
.build()
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,143 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.home_mixer.functional_component.candidate_source.EarlybirdCandidateSource
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.HomeFeedbackActionInfoBuilder
|
||||
import com.twitter.home_mixer.functional_component.filter.DropMaxCandidatesFilter
|
||||
import com.twitter.home_mixer.functional_component.filter.PreviouslyServedTweetPreviewsFilter
|
||||
import com.twitter.home_mixer.functional_component.gate.TimelinesPersistenceStoreLastInjectionGate
|
||||
import com.twitter.home_mixer.model.HomeFeatures.AuthorEnabledPreviewsFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.IsHydratedFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.PersistenceEntriesFeature
|
||||
import com.twitter.home_mixer.product.for_you.feature_hydrator.AuthorEnabledPreviewsFeatureHydrator
|
||||
import com.twitter.home_mixer.product.for_you.feature_hydrator.TweetPreviewTweetypieCandidateFeatureHydrator
|
||||
import com.twitter.home_mixer.product.for_you.filter.TweetPreviewTextFilter
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.EnableTweetPreviewsCandidatePipelineParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.TweetPreviewsMaxCandidatesParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.TweetPreviewsMinInjectionIntervalParam
|
||||
import com.twitter.home_mixer.product.for_you.query_transformer.TweetPreviewsQueryTransformer
|
||||
import com.twitter.home_mixer.product.for_you.response_transformer.TweetPreviewResponseFeatureTransformer
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.UrtItemCandidateDecorator
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.contextual_ref.ContextualTweetRefBuilder
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.tweet.TweetCandidateUrtItemBuilder
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.metadata.ClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.social_graph.PreviewCreatorsFeature
|
||||
import com.twitter.product_mixer.component_library.filter.FeatureFilter
|
||||
import com.twitter.product_mixer.component_library.gate.NonEmptySeqFeatureGate
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSourceWithExtractedFeatures
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.CandidateDecorator
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.BaseCandidateFeatureHydrator
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.gate.Gate
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidateFeatureTransformer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidatePipelineQueryTransformer
|
||||
import com.twitter.product_mixer.core.functional_component.transformer.CandidatePipelineResultsTransformer
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidatePipelineIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.rtf.safety_level.TimelineHomeTweetPreviewHydrationSafetyLevel
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.contextual_ref.TweetHydrationContext
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.core.pipeline.candidate.CandidatePipelineConfig
|
||||
import com.twitter.search.earlybird.{thriftscala => eb}
|
||||
import com.twitter.timelines.configapi.FSParam
|
||||
import com.twitter.timelines.injection.scribe.InjectionScribeUtil
|
||||
import com.twitter.timelineservice.model.rich.EntityIdType
|
||||
import com.twitter.timelineservice.suggests.{thriftscala => st}
|
||||
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ForYouTweetPreviewsCandidatePipelineConfig @Inject() (
|
||||
earlybirdCandidateSource: EarlybirdCandidateSource,
|
||||
authorEnabledPreviewsFeatureHydrator: AuthorEnabledPreviewsFeatureHydrator,
|
||||
tweetPreviewsQueryTransformer: TweetPreviewsQueryTransformer,
|
||||
tweetPreviewTweetypieCandidateFeatureHydrator: TweetPreviewTweetypieCandidateFeatureHydrator,
|
||||
homeFeedbackActionInfoBuilder: HomeFeedbackActionInfoBuilder)
|
||||
extends CandidatePipelineConfig[
|
||||
ForYouQuery,
|
||||
eb.EarlybirdRequest,
|
||||
eb.ThriftSearchResult,
|
||||
TweetCandidate
|
||||
] {
|
||||
|
||||
val identifier: CandidatePipelineIdentifier = CandidatePipelineIdentifier("ForYouTweetPreviews")
|
||||
|
||||
override val supportedClientParam: Option[FSParam[Boolean]] =
|
||||
Some(EnableTweetPreviewsCandidatePipelineParam)
|
||||
|
||||
override val gates: Seq[Gate[ForYouQuery]] = {
|
||||
Seq(
|
||||
TimelinesPersistenceStoreLastInjectionGate(
|
||||
TweetPreviewsMinInjectionIntervalParam,
|
||||
PersistenceEntriesFeature,
|
||||
EntityIdType.TweetPreview
|
||||
),
|
||||
NonEmptySeqFeatureGate(PreviewCreatorsFeature)
|
||||
)
|
||||
}
|
||||
|
||||
override val queryTransformer: CandidatePipelineQueryTransformer[
|
||||
PipelineQuery,
|
||||
eb.EarlybirdRequest
|
||||
] = tweetPreviewsQueryTransformer
|
||||
|
||||
override val candidateSource: CandidateSourceWithExtractedFeatures[
|
||||
eb.EarlybirdRequest,
|
||||
eb.ThriftSearchResult
|
||||
] = earlybirdCandidateSource
|
||||
|
||||
override val featuresFromCandidateSourceTransformers: Seq[
|
||||
CandidateFeatureTransformer[eb.ThriftSearchResult]
|
||||
] = Seq(TweetPreviewResponseFeatureTransformer)
|
||||
|
||||
override val resultTransformer: CandidatePipelineResultsTransformer[
|
||||
eb.ThriftSearchResult,
|
||||
TweetCandidate
|
||||
] = { tweet => TweetCandidate(tweet.id) }
|
||||
|
||||
override val preFilterFeatureHydrationPhase1: Seq[
|
||||
BaseCandidateFeatureHydrator[ForYouQuery, TweetCandidate, _]
|
||||
] = Seq(
|
||||
authorEnabledPreviewsFeatureHydrator,
|
||||
tweetPreviewTweetypieCandidateFeatureHydrator,
|
||||
)
|
||||
|
||||
override val filters: Seq[
|
||||
Filter[ForYouQuery, TweetCandidate]
|
||||
] = Seq(
|
||||
PreviouslyServedTweetPreviewsFilter,
|
||||
FeatureFilter
|
||||
.fromFeature(FilterIdentifier("TweetPreviewVisibilityFiltering"), IsHydratedFeature),
|
||||
FeatureFilter
|
||||
.fromFeature(FilterIdentifier("AuthorEnabledPreviews"), AuthorEnabledPreviewsFeature),
|
||||
TweetPreviewTextFilter,
|
||||
DropMaxCandidatesFilter(TweetPreviewsMaxCandidatesParam)
|
||||
)
|
||||
|
||||
override val decorator: Option[CandidateDecorator[PipelineQuery, TweetCandidate]] = {
|
||||
val component = InjectionScribeUtil.scribeComponent(st.SuggestType.TweetPreview).get
|
||||
val clientEventInfoBuilder = ClientEventInfoBuilder[PipelineQuery, TweetCandidate](component)
|
||||
|
||||
val tweetItemBuilder = TweetCandidateUrtItemBuilder[PipelineQuery, TweetCandidate](
|
||||
clientEventInfoBuilder = clientEventInfoBuilder,
|
||||
contextualTweetRefBuilder = Some(
|
||||
ContextualTweetRefBuilder(
|
||||
TweetHydrationContext(
|
||||
safetyLevelOverride = Some(TimelineHomeTweetPreviewHydrationSafetyLevel),
|
||||
outerTweetContext = None
|
||||
)
|
||||
)
|
||||
),
|
||||
feedbackActionInfoBuilder = Some(homeFeedbackActionInfoBuilder),
|
||||
)
|
||||
|
||||
Some(UrtItemCandidateDecorator(tweetItemBuilder))
|
||||
}
|
||||
|
||||
override val alerts: Seq[Alert] = Seq(
|
||||
HomeMixerAlertConfig.BusinessHours.defaultSuccessRateAlert(95))
|
||||
}
|
Binary file not shown.
@ -1,56 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.HomeWhoToFollowFeedbackActionInfoBuilder
|
||||
import com.twitter.home_mixer.functional_component.gate.DismissFatigueGate
|
||||
import com.twitter.home_mixer.functional_component.gate.TimelinesPersistenceStoreLastInjectionGate
|
||||
import com.twitter.home_mixer.model.HomeFeatures.DismissInfoFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.PersistenceEntriesFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.WhoToFollowExcludedUserIdsFeature
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.EnableWhoToFollowCandidatePipelineParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.WhoToFollowDisplayLocationParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.WhoToFollowDisplayTypeIdParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.WhoToFollowMinInjectionIntervalParam
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.ParamWhoToFollowModuleDisplayTypeBuilder
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.who_to_follow_module.WhoToFollowArmCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.who_to_follow_module.WhoToFollowArmCandidatePipelineConfigBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.gate.Gate
|
||||
import com.twitter.timelineservice.model.rich.EntityIdType
|
||||
import com.twitter.timelineservice.suggests.thriftscala.SuggestType
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ForYouWhoToFollowCandidatePipelineConfigBuilder @Inject() (
|
||||
whoToFollowArmCandidatePipelineConfigBuilder: WhoToFollowArmCandidatePipelineConfigBuilder,
|
||||
homeWhoToFollowFeedbackActionInfoBuilder: HomeWhoToFollowFeedbackActionInfoBuilder) {
|
||||
|
||||
def build(): WhoToFollowArmCandidatePipelineConfig[ForYouQuery] = {
|
||||
val gates: Seq[Gate[ForYouQuery]] = Seq(
|
||||
TimelinesPersistenceStoreLastInjectionGate(
|
||||
WhoToFollowMinInjectionIntervalParam,
|
||||
PersistenceEntriesFeature,
|
||||
EntityIdType.WhoToFollow
|
||||
),
|
||||
DismissFatigueGate(SuggestType.WhoToFollow, DismissInfoFeature)
|
||||
)
|
||||
|
||||
whoToFollowArmCandidatePipelineConfigBuilder.build[ForYouQuery](
|
||||
identifier = WhoToFollowArmCandidatePipelineConfig.identifier,
|
||||
supportedClientParam = Some(EnableWhoToFollowCandidatePipelineParam),
|
||||
alerts = alerts,
|
||||
gates = gates,
|
||||
moduleDisplayTypeBuilder =
|
||||
ParamWhoToFollowModuleDisplayTypeBuilder(WhoToFollowDisplayTypeIdParam),
|
||||
feedbackActionInfoBuilder = Some(homeWhoToFollowFeedbackActionInfoBuilder),
|
||||
excludedUserIdsFeature = Some(WhoToFollowExcludedUserIdsFeature),
|
||||
displayLocationParam = WhoToFollowDisplayLocationParam
|
||||
)
|
||||
}
|
||||
|
||||
private val alerts = Seq(
|
||||
HomeMixerAlertConfig.BusinessHours.defaultSuccessRateAlert(70),
|
||||
HomeMixerAlertConfig.BusinessHours.defaultEmptyResponseRateAlert()
|
||||
)
|
||||
}
|
Binary file not shown.
@ -1,52 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you
|
||||
|
||||
import com.twitter.home_mixer.functional_component.decorator.urt.builder.HomeWhoToSubscribeFeedbackActionInfoBuilder
|
||||
import com.twitter.home_mixer.functional_component.gate.DismissFatigueGate
|
||||
import com.twitter.home_mixer.functional_component.gate.TimelinesPersistenceStoreLastInjectionGate
|
||||
import com.twitter.home_mixer.model.HomeFeatures.DismissInfoFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.PersistenceEntriesFeature
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.EnableWhoToSubscribeCandidatePipelineParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.WhoToSubscribeDisplayTypeIdParam
|
||||
import com.twitter.home_mixer.product.for_you.param.ForYouParam.WhoToSubscribeMinInjectionIntervalParam
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.ParamWhoToFollowModuleDisplayTypeBuilder
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.who_to_subscribe_module.WhoToSubscribeCandidatePipelineConfig
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.who_to_subscribe_module.WhoToSubscribeCandidatePipelineConfigBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.gate.Gate
|
||||
import com.twitter.timelineservice.model.rich.EntityIdType
|
||||
import com.twitter.timelineservice.suggests.thriftscala.SuggestType
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ForYouWhoToSubscribeCandidatePipelineConfigBuilder @Inject() (
|
||||
whoToSubscribeCandidatePipelineConfigBuilder: WhoToSubscribeCandidatePipelineConfigBuilder,
|
||||
homeWhoToSubscribeFeedbackActionInfoBuilder: HomeWhoToSubscribeFeedbackActionInfoBuilder) {
|
||||
|
||||
def build(): WhoToSubscribeCandidatePipelineConfig[ForYouQuery] = {
|
||||
val gates: Seq[Gate[ForYouQuery]] = Seq(
|
||||
TimelinesPersistenceStoreLastInjectionGate(
|
||||
WhoToSubscribeMinInjectionIntervalParam,
|
||||
PersistenceEntriesFeature,
|
||||
EntityIdType.WhoToSubscribe
|
||||
),
|
||||
DismissFatigueGate(SuggestType.WhoToSubscribe, DismissInfoFeature)
|
||||
)
|
||||
|
||||
whoToSubscribeCandidatePipelineConfigBuilder.build[ForYouQuery](
|
||||
identifier = WhoToSubscribeCandidatePipelineConfig.identifier,
|
||||
supportedClientParam = Some(EnableWhoToSubscribeCandidatePipelineParam),
|
||||
alerts = alerts,
|
||||
gates = gates,
|
||||
moduleDisplayTypeBuilder =
|
||||
ParamWhoToFollowModuleDisplayTypeBuilder(WhoToSubscribeDisplayTypeIdParam),
|
||||
feedbackActionInfoBuilder = Some(homeWhoToSubscribeFeedbackActionInfoBuilder)
|
||||
)
|
||||
}
|
||||
|
||||
private val alerts = Seq(
|
||||
HomeMixerAlertConfig.BusinessHours.defaultSuccessRateAlert(70),
|
||||
HomeMixerAlertConfig.BusinessHours.defaultEmptyResponseRateAlert()
|
||||
)
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"finatra/inject/inject-core/src/main/scala",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model/request",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/model",
|
||||
"home-mixer/thrift/src/main/thrift:thrift-scala",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/cursor",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/premarshaller/urt",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/product_pipeline",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"src/thrift/com/twitter/search:earlybird-scala",
|
||||
"stitch/stitch-timelineservice/src/main/scala",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,174 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you.candidate_source
|
||||
|
||||
import com.google.inject.Provider
|
||||
import com.twitter.home_mixer.model.HomeFeatures.ServedTweetIdsFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.TimelineServiceTweetsFeature
|
||||
import com.twitter.home_mixer.model.request.HomeMixerRequest
|
||||
import com.twitter.home_mixer.model.request.ScoredTweetsProduct
|
||||
import com.twitter.home_mixer.model.request.ScoredTweetsProductContext
|
||||
import com.twitter.home_mixer.product.for_you.model.ForYouQuery
|
||||
import com.twitter.home_mixer.{thriftscala => t}
|
||||
import com.twitter.product_mixer.component_library.premarshaller.cursor.UrtCursorSerializer
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.product_pipeline.ProductPipelineCandidateSource
|
||||
import com.twitter.product_mixer.core.functional_component.configapi.ParamsBuilder
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.product_mixer.core.product.registry.ProductPipelineRegistry
|
||||
import com.twitter.timelines.render.{thriftscala => tl}
|
||||
import com.twitter.timelineservice.suggests.{thriftscala => st}
|
||||
import com.twitter.tweetconvosvc.tweet_ancestor.{thriftscala => ta}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* [[ScoredTweetWithConversationMetadata]]
|
||||
**/
|
||||
case class ScoredTweetWithConversationMetadata(
|
||||
tweetId: Long,
|
||||
authorId: Long,
|
||||
score: Option[Double] = None,
|
||||
suggestType: Option[st.SuggestType] = None,
|
||||
sourceTweetId: Option[Long] = None,
|
||||
sourceUserId: Option[Long] = None,
|
||||
quotedTweetId: Option[Long] = None,
|
||||
quotedUserId: Option[Long] = None,
|
||||
inReplyToTweetId: Option[Long] = None,
|
||||
inReplyToUserId: Option[Long] = None,
|
||||
directedAtUserId: Option[Long] = None,
|
||||
inNetwork: Option[Boolean] = None,
|
||||
sgsValidLikedByUserIds: Option[Seq[Long]] = None,
|
||||
sgsValidFollowedByUserIds: Option[Seq[Long]] = None,
|
||||
ancestors: Option[Seq[ta.TweetAncestor]] = None,
|
||||
topicId: Option[Long] = None,
|
||||
topicFunctionalityType: Option[tl.TopicContextFunctionalityType] = None,
|
||||
conversationId: Option[Long] = None,
|
||||
conversationFocalTweetId: Option[Long] = None,
|
||||
isReadFromCache: Option[Boolean] = None,
|
||||
streamToKafka: Option[Boolean] = None,
|
||||
exclusiveConversationAuthorId: Option[Long] = None,
|
||||
authorIsBlueVerified: Option[Boolean] = None,
|
||||
authorIsGoldVerified: Option[Boolean] = None,
|
||||
authorIsGrayVerified: Option[Boolean] = None,
|
||||
authorIsLegacyVerified: Option[Boolean] = None,
|
||||
authorIsCreator: Option[Boolean] = None,
|
||||
perspectiveFilteredLikedByUserIds: Option[Seq[Long]] = None)
|
||||
|
||||
@Singleton
|
||||
class ScoredTweetsProductCandidateSource @Inject() (
|
||||
override val productPipelineRegistry: Provider[ProductPipelineRegistry],
|
||||
override val paramsBuilder: Provider[ParamsBuilder])
|
||||
extends ProductPipelineCandidateSource[
|
||||
ForYouQuery,
|
||||
HomeMixerRequest,
|
||||
t.ScoredTweetsResponse,
|
||||
ScoredTweetWithConversationMetadata
|
||||
] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier("ScoredTweetsProduct")
|
||||
|
||||
private val MaxModuleSize = 3
|
||||
private val MaxAncestorsInConversation = 2
|
||||
|
||||
override def pipelineRequestTransformer(productPipelineQuery: ForYouQuery): HomeMixerRequest = {
|
||||
HomeMixerRequest(
|
||||
clientContext = productPipelineQuery.clientContext,
|
||||
product = ScoredTweetsProduct,
|
||||
productContext = Some(
|
||||
ScoredTweetsProductContext(
|
||||
productPipelineQuery.deviceContext,
|
||||
productPipelineQuery.seenTweetIds,
|
||||
productPipelineQuery.features.map(_.getOrElse(ServedTweetIdsFeature, Seq.empty)),
|
||||
productPipelineQuery.features.map(_.getOrElse(TimelineServiceTweetsFeature, Seq.empty))
|
||||
)),
|
||||
serializedRequestCursor =
|
||||
productPipelineQuery.pipelineCursor.map(UrtCursorSerializer.serializeCursor),
|
||||
maxResults = productPipelineQuery.requestedMaxResults,
|
||||
debugParams = None,
|
||||
homeRequestParam = false
|
||||
)
|
||||
}
|
||||
|
||||
override def productPipelineResultTransformer(
|
||||
productPipelineResult: t.ScoredTweetsResponse
|
||||
): Seq[ScoredTweetWithConversationMetadata] = {
|
||||
val scoredTweets = productPipelineResult.scoredTweets.flatMap { focalTweet =>
|
||||
val parentTweets = focalTweet.ancestors.getOrElse(Seq.empty).sortBy(-_.tweetId)
|
||||
val (intermediates, root) = parentTweets.splitAt(parentTweets.size - 1)
|
||||
val truncatedIntermediates =
|
||||
intermediates.take(MaxModuleSize - MaxAncestorsInConversation).reverse
|
||||
val rootScoredTweet: Seq[ScoredTweetWithConversationMetadata] = root.map { ancestor =>
|
||||
ScoredTweetWithConversationMetadata(
|
||||
tweetId = ancestor.tweetId,
|
||||
authorId = ancestor.userId,
|
||||
suggestType = focalTweet.suggestType,
|
||||
conversationId = Some(ancestor.tweetId),
|
||||
conversationFocalTweetId = Some(focalTweet.tweetId),
|
||||
exclusiveConversationAuthorId = focalTweet.exclusiveConversationAuthorId
|
||||
)
|
||||
}
|
||||
val conversationId = rootScoredTweet.headOption.map(_.tweetId)
|
||||
|
||||
val tweetsToParents =
|
||||
if (parentTweets.nonEmpty) parentTweets.zip(parentTweets.tail).toMap
|
||||
else Map.empty[ta.TweetAncestor, ta.TweetAncestor]
|
||||
|
||||
val intermediateScoredTweets = truncatedIntermediates.map { ancestor =>
|
||||
ScoredTweetWithConversationMetadata(
|
||||
tweetId = ancestor.tweetId,
|
||||
authorId = ancestor.userId,
|
||||
suggestType = focalTweet.suggestType,
|
||||
inReplyToTweetId = tweetsToParents.get(ancestor).map(_.tweetId),
|
||||
conversationId = conversationId,
|
||||
conversationFocalTweetId = Some(focalTweet.tweetId),
|
||||
exclusiveConversationAuthorId = focalTweet.exclusiveConversationAuthorId
|
||||
)
|
||||
}
|
||||
val parentScoredTweets = rootScoredTweet ++ intermediateScoredTweets
|
||||
|
||||
val conversationFocalTweetId =
|
||||
if (parentScoredTweets.nonEmpty) Some(focalTweet.tweetId) else None
|
||||
|
||||
val focalScoredTweet = ScoredTweetWithConversationMetadata(
|
||||
tweetId = focalTweet.tweetId,
|
||||
authorId = focalTweet.authorId,
|
||||
score = focalTweet.score,
|
||||
suggestType = focalTweet.suggestType,
|
||||
sourceTweetId = focalTweet.sourceTweetId,
|
||||
sourceUserId = focalTweet.sourceUserId,
|
||||
quotedTweetId = focalTweet.quotedTweetId,
|
||||
quotedUserId = focalTweet.quotedUserId,
|
||||
inReplyToTweetId = parentScoredTweets.lastOption.map(_.tweetId),
|
||||
inReplyToUserId = focalTweet.inReplyToUserId,
|
||||
directedAtUserId = focalTweet.directedAtUserId,
|
||||
inNetwork = focalTweet.inNetwork,
|
||||
sgsValidLikedByUserIds = focalTweet.sgsValidLikedByUserIds,
|
||||
sgsValidFollowedByUserIds = focalTweet.sgsValidFollowedByUserIds,
|
||||
topicId = focalTweet.topicId,
|
||||
topicFunctionalityType = focalTweet.topicFunctionalityType,
|
||||
ancestors = focalTweet.ancestors,
|
||||
conversationId = conversationId,
|
||||
conversationFocalTweetId = conversationFocalTweetId,
|
||||
isReadFromCache = focalTweet.isReadFromCache,
|
||||
streamToKafka = focalTweet.streamToKafka,
|
||||
exclusiveConversationAuthorId = focalTweet.exclusiveConversationAuthorId,
|
||||
authorIsBlueVerified = focalTweet.authorMetadata.map(_.blueVerified),
|
||||
authorIsGoldVerified = focalTweet.authorMetadata.map(_.goldVerified),
|
||||
authorIsGrayVerified = focalTweet.authorMetadata.map(_.grayVerified),
|
||||
authorIsLegacyVerified = focalTweet.authorMetadata.map(_.legacyVerified),
|
||||
authorIsCreator = focalTweet.authorMetadata.map(_.creator),
|
||||
perspectiveFilteredLikedByUserIds = focalTweet.perspectiveFilteredLikedByUserIds
|
||||
)
|
||||
|
||||
parentScoredTweets :+ focalScoredTweet
|
||||
}
|
||||
|
||||
val dedupedTweets = scoredTweets.groupBy(_.tweetId).map {
|
||||
case (_, duplicateAncestors) => duplicateAncestors.maxBy(_.score.getOrElse(0.0))
|
||||
}
|
||||
|
||||
// Sort by tweet id to prevent issues with future assumptions of the root being the first
|
||||
// tweet and the focal being the last tweet in a module. The tweets as a whole do not need
|
||||
// to be sorted overall, only the relative order within modules must be kept.
|
||||
dedupedTweets.toSeq.sortBy(_.tweetId)
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,72 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you.feature_hydrator
|
||||
|
||||
import com.twitter.home_mixer.model.HomeFeatures.AuthorIdFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.AuthorEnabledPreviewsFeature
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.BulkCandidateFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.strato.generated.client.audiencerewards.audienceRewardsService.GetCreatorPreferencesOnUserClientColumn
|
||||
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Hydrates the `AuthorEnabledPreviews` feature for tweets authored by creators by querying the
|
||||
* `GetCreatorPreferences` Strato column. This feature corresponds to the `previews_enabled` field of that column.
|
||||
* Given a tweet from a creator, this feature indicates whether that creator has enabled previews
|
||||
* on their profile.
|
||||
*/
|
||||
@Singleton
|
||||
class AuthorEnabledPreviewsFeatureHydrator @Inject() (
|
||||
getCreatorPreferencesOnUserClientColumn: GetCreatorPreferencesOnUserClientColumn)
|
||||
extends BulkCandidateFeatureHydrator[PipelineQuery, TweetCandidate] {
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier =
|
||||
FeatureHydratorIdentifier("AuthorEnabledPreviews")
|
||||
|
||||
override val features: Set[Feature[_, _]] = Set(AuthorEnabledPreviewsFeature)
|
||||
|
||||
private val fetcher = getCreatorPreferencesOnUserClientColumn.fetcher
|
||||
|
||||
private val DefaultAuthorEnabledPreviewsValue = true
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[TweetCandidate]]
|
||||
): Stitch[Seq[FeatureMap]] = {
|
||||
val candidateAuthors = candidates
|
||||
.map(_.features.getOrElse(AuthorIdFeature, None))
|
||||
.toSet
|
||||
.flatten
|
||||
|
||||
// Build a map of creator -> authorEnabledPreviews, then use it to populate candidate features
|
||||
val authorIdToFeatureStitch = Stitch.collect {
|
||||
candidateAuthors
|
||||
.map { author =>
|
||||
val isAuthorEnabledPreviews = fetcher.fetch(author).map {
|
||||
_.v.map(_.previewsEnabled).getOrElse(DefaultAuthorEnabledPreviewsValue)
|
||||
}
|
||||
(author, isAuthorEnabledPreviews)
|
||||
}.toMap
|
||||
}
|
||||
|
||||
authorIdToFeatureStitch.map { authorIdToFeatureMap =>
|
||||
candidates.map {
|
||||
_.features.getOrElse(AuthorIdFeature, None) match {
|
||||
case Some(authorId) => FeatureMapBuilder()
|
||||
.add(AuthorEnabledPreviewsFeature, authorIdToFeatureMap(authorId))
|
||||
.build()
|
||||
case _ => FeatureMapBuilder()
|
||||
.add(AuthorEnabledPreviewsFeature, DefaultAuthorEnabledPreviewsValue)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"audience-rewards/thrift/src/main/thrift/common:thrift-scala",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/marshaller/timelines",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model/request",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/param",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/service",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/util",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/candidate/tweet_tweetypie",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/util",
|
||||
"src/thrift/com/twitter/socialgraph:thrift-scala",
|
||||
"stitch/stitch-core",
|
||||
"stitch/stitch-socialgraph",
|
||||
"stitch/stitch-timelineservice",
|
||||
"strato/config/columns/audiencerewards/audienceRewardsService:audienceRewardsService-strato-client",
|
||||
"strato/src/main/scala/com/twitter/strato/client",
|
||||
"timelinemixer/common/src/main/scala/com/twitter/timelinemixer/clients/feedback",
|
||||
"timelineservice/common/src/main/scala/com/twitter/timelineservice/model",
|
||||
"util/util-core",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,84 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you.feature_hydrator
|
||||
|
||||
import com.twitter.home_mixer.model.HomeFeatures.AuthorIdFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.ConversationModuleFocalTweetIdFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.ConversationModuleIdFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.FocalTweetAuthorIdFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.FocalTweetInNetworkFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.FocalTweetRealNamesFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.FocalTweetScreenNamesFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.InNetworkFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.RealNamesFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.ScreenNamesFeature
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.BulkCandidateFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Social context for convo modules is hydrated on the root Tweet but needs info about the focal
|
||||
* Tweet (e.g. author) to render the banner. This hydrator copies focal Tweet data into the root.
|
||||
*/
|
||||
@Singleton
|
||||
class FocalTweetFeatureHydrator @Inject() ()
|
||||
extends BulkCandidateFeatureHydrator[PipelineQuery, TweetCandidate] {
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier("FocalTweet")
|
||||
|
||||
override val features: Set[Feature[_, _]] = Set(
|
||||
FocalTweetAuthorIdFeature,
|
||||
FocalTweetInNetworkFeature,
|
||||
FocalTweetRealNamesFeature,
|
||||
FocalTweetScreenNamesFeature
|
||||
)
|
||||
|
||||
private val DefaultFeatureMap = FeatureMapBuilder()
|
||||
.add(FocalTweetAuthorIdFeature, None)
|
||||
.add(FocalTweetInNetworkFeature, None)
|
||||
.add(FocalTweetRealNamesFeature, None)
|
||||
.add(FocalTweetScreenNamesFeature, None)
|
||||
.build()
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[TweetCandidate]]
|
||||
): Stitch[Seq[FeatureMap]] = {
|
||||
// Build a map of all the focal tweets to their corresponding features
|
||||
val focalTweetIdToFeatureMap = candidates.flatMap { candidate =>
|
||||
val focalTweetId = candidate.features.getOrElse(ConversationModuleFocalTweetIdFeature, None)
|
||||
if (focalTweetId.contains(candidate.candidate.id)) {
|
||||
Some(candidate.candidate.id -> candidate.features)
|
||||
} else None
|
||||
}.toMap
|
||||
|
||||
val updatedFeatureMap = candidates.map { candidate =>
|
||||
val focalTweetId = candidate.features.getOrElse(ConversationModuleFocalTweetIdFeature, None)
|
||||
val conversationId = candidate.features.getOrElse(ConversationModuleIdFeature, None)
|
||||
|
||||
// Check if the candidate is a root tweet and ensure its focal tweet's features are available
|
||||
if (conversationId.contains(candidate.candidate.id)
|
||||
&& focalTweetId.exists(focalTweetIdToFeatureMap.contains)) {
|
||||
val featureMap = focalTweetIdToFeatureMap.get(focalTweetId.get).get
|
||||
FeatureMapBuilder()
|
||||
.add(FocalTweetAuthorIdFeature, featureMap.getOrElse(AuthorIdFeature, None))
|
||||
.add(FocalTweetInNetworkFeature, Some(featureMap.getOrElse(InNetworkFeature, true)))
|
||||
.add(
|
||||
FocalTweetRealNamesFeature,
|
||||
Some(featureMap.getOrElse(RealNamesFeature, Map.empty[Long, String])))
|
||||
.add(
|
||||
FocalTweetScreenNamesFeature,
|
||||
Some(featureMap.getOrElse(ScreenNamesFeature, Map.empty[Long, String])))
|
||||
.build()
|
||||
} else DefaultFeatureMap
|
||||
}
|
||||
|
||||
Stitch.value(updatedFeatureMap)
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,62 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you.feature_hydrator
|
||||
|
||||
import com.twitter.home_mixer.marshaller.timelines.DeviceContextMarshaller
|
||||
import com.twitter.home_mixer.model.HomeFeatures.TimelineServiceTweetsFeature
|
||||
import com.twitter.home_mixer.model.request.DeviceContext
|
||||
import com.twitter.home_mixer.model.request.HasDeviceContext
|
||||
import com.twitter.home_mixer.service.HomeMixerAlertConfig
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.stitch.timelineservice.TimelineService
|
||||
import com.twitter.timelineservice.{thriftscala => t}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
case class TimelineServiceTweetsQueryFeatureHydrator @Inject() (
|
||||
timelineService: TimelineService,
|
||||
deviceContextMarshaller: DeviceContextMarshaller)
|
||||
extends QueryFeatureHydrator[PipelineQuery with HasDeviceContext] {
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier =
|
||||
FeatureHydratorIdentifier("TimelineServiceTweets")
|
||||
|
||||
override val features: Set[Feature[_, _]] = Set(TimelineServiceTweetsFeature)
|
||||
|
||||
private val MaxTimelineServiceTweets = 200
|
||||
|
||||
override def hydrate(query: PipelineQuery with HasDeviceContext): Stitch[FeatureMap] = {
|
||||
val deviceContext = query.deviceContext.getOrElse(DeviceContext.Empty)
|
||||
|
||||
val timelineQueryOptions = t.TimelineQueryOptions(
|
||||
contextualUserId = query.clientContext.userId,
|
||||
deviceContext = Some(deviceContextMarshaller(deviceContext, query.clientContext))
|
||||
)
|
||||
|
||||
val timelineServiceQuery = t.TimelineQuery(
|
||||
timelineType = t.TimelineType.Home,
|
||||
timelineId = query.getRequiredUserId,
|
||||
maxCount = MaxTimelineServiceTweets.toShort,
|
||||
cursor2 = None,
|
||||
options = Some(timelineQueryOptions),
|
||||
timelineId2 = query.clientContext.userId.map(t.TimelineId(t.TimelineType.Home, _, None)),
|
||||
)
|
||||
|
||||
timelineService.getTimeline(timelineServiceQuery).map { timeline =>
|
||||
val tweets = timeline.entries.collect {
|
||||
case t.TimelineEntry.Tweet(tweet) => tweet.statusId
|
||||
}
|
||||
|
||||
FeatureMapBuilder().add(TimelineServiceTweetsFeature, tweets).build()
|
||||
}
|
||||
}
|
||||
|
||||
override val alerts = Seq(
|
||||
HomeMixerAlertConfig.BusinessHours.defaultSuccessRateAlert(99.7)
|
||||
)
|
||||
}
|
Binary file not shown.
@ -1,72 +0,0 @@
|
||||
package com.twitter.home_mixer.product.for_you.feature_hydrator
|
||||
|
||||
import com.twitter.home_mixer.model.HomeFeatures.AuthorIdFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.IsHydratedFeature
|
||||
import com.twitter.home_mixer.model.HomeFeatures.TweetTextFeature
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.CandidateFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.spam.rtf.{thriftscala => rtf}
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.stitch.tweetypie.{TweetyPie => TweetypieStitchClient}
|
||||
import com.twitter.tweetypie.{thriftscala => TP}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class TweetPreviewTweetypieCandidateFeatureHydrator @Inject() (
|
||||
tweetypieStitchClient: TweetypieStitchClient)
|
||||
extends CandidateFeatureHydrator[PipelineQuery, BaseTweetCandidate] {
|
||||
|
||||
private val CoreTweetFields: Set[TP.TweetInclude] = Set[TP.TweetInclude](
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.IdField.id),
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.CoreDataField.id)
|
||||
)
|
||||
|
||||
private val DefaultFeatureMap = FeatureMapBuilder()
|
||||
.add(TweetTextFeature, None)
|
||||
.add(IsHydratedFeature, false)
|
||||
.add(AuthorIdFeature, None)
|
||||
.build()
|
||||
|
||||
override val features: Set[Feature[_, _]] =
|
||||
Set(TweetTextFeature, IsHydratedFeature, AuthorIdFeature)
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier =
|
||||
FeatureHydratorIdentifier("TweetPreviewTweetypie")
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidate: BaseTweetCandidate,
|
||||
existingFeatures: FeatureMap
|
||||
): Stitch[FeatureMap] = {
|
||||
tweetypieStitchClient
|
||||
.getTweetFields(
|
||||
tweetId = candidate.id,
|
||||
options = TP.GetTweetFieldsOptions(
|
||||
tweetIncludes = CoreTweetFields,
|
||||
includeRetweetedTweet = false,
|
||||
includeQuotedTweet = false,
|
||||
visibilityPolicy = TP.TweetVisibilityPolicy.UserVisible,
|
||||
safetyLevel = Some(rtf.SafetyLevel.TimelineHomeTweetPreview),
|
||||
forUserId = query.getOptionalUserId
|
||||
)
|
||||
).map {
|
||||
case TP.GetTweetFieldsResult(_, TP.TweetFieldsResultState.Found(found), quoteOpt, _) =>
|
||||
val tweetText = found.tweet.coreData.map(_.text)
|
||||
FeatureMapBuilder()
|
||||
.add(TweetTextFeature, tweetText)
|
||||
.add(IsHydratedFeature, true)
|
||||
.add(AuthorIdFeature, found.tweet.coreData.map(_.userId))
|
||||
.build()
|
||||
// If no tweet result found, return default features
|
||||
case _ =>
|
||||
DefaultFeatureMap
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/model",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/product/for_you/param",
|
||||
"home-mixer/server/src/main/scala/com/twitter/home_mixer/util",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/filter",
|
||||
"stitch/stitch-core",
|
||||
"timelineservice/common/src/main/scala/com/twitter/timelineservice/model",
|
||||
],
|
||||
)
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user