Delete product-mixer directory
This commit is contained in:
parent
61c1dd624f
commit
7e7475fc05
|
@ -1,41 +0,0 @@
|
|||
Product Mixer
|
||||
=============
|
||||
|
||||
## Overview
|
||||
|
||||
Product Mixer is a common service framework and set of libraries that make it easy to build,
|
||||
iterate on, and own product surface areas. It consists of:
|
||||
|
||||
- **Core Libraries:** A set of libraries that enable you to build execution pipelines out of
|
||||
reusable components. You define your logic in small, well-defined, reusable components and focus
|
||||
on expressing the business logic you want to have. Then you can define easy to understand pipelines
|
||||
that compose your components. Product Mixer handles the execution and monitoring of your pipelines
|
||||
allowing you to focus on what really matters, your business logic.
|
||||
|
||||
- **Service Framework:** A common service skeleton for teams to host their Product Mixer products.
|
||||
|
||||
- **Component Library:** A shared library of components made by the Product Mixer Team, or
|
||||
contributed by users. This enables you to both easily share the reusable components you make as well
|
||||
as benefit from the work other teams have done by utilizing their shared components in the library.
|
||||
|
||||
## Architecture
|
||||
|
||||
The bulk of a Product Mixer can be broken down into Pipelines and Components. Components allow you
|
||||
to break business logic into separate, standardized, reusable, testable, and easily composable
|
||||
pieces, where each component has a well defined abstraction. Pipelines are essentially configuration
|
||||
files specifying which Components should be used and when. This makes it easy to understand how your
|
||||
code will execute while keeping it organized and structured in a maintainable way.
|
||||
|
||||
Requests first go to Product Pipelines, which are used to select which Mixer Pipeline or
|
||||
Recommendation Pipeline to run for a given request. Each Mixer or Recommendation
|
||||
Pipeline may run multiple Candidate Pipelines to fetch candidates to include in the response.
|
||||
|
||||
Mixer Pipelines combine the results of multiple heterogeneous Candidate Pipelines together
|
||||
(e.g. ads, tweets, users) while Recommendation Pipelines are used to score (via Scoring Pipelines)
|
||||
and rank the results of homogenous Candidate Pipelines so that the top ranked ones can be returned.
|
||||
These pipelines also marshall candidates into a domain object and then into a transport object
|
||||
to return to the caller.
|
||||
|
||||
Candidate Pipelines fetch candidates from underlying Candidate Sources and perform some basic
|
||||
operations on the Candidates, such as filtering out unwanted candidates, applying decorations,
|
||||
and hydrating features.
|
|
@ -1,57 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.account_recommendations_mixer
|
||||
|
||||
import com.twitter.account_recommendations_mixer.{thriftscala => t}
|
||||
import com.twitter.product_mixer.component_library.model.candidate.UserCandidate
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSourceWithExtractedFeatures
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidatesWithSourceFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.stitch.Stitch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
object WhoToFollowModuleHeaderFeature extends Feature[UserCandidate, t.Header]
|
||||
object WhoToFollowModuleFooterFeature extends Feature[UserCandidate, Option[t.Footer]]
|
||||
object WhoToFollowModuleDisplayOptionsFeature
|
||||
extends Feature[UserCandidate, Option[t.DisplayOptions]]
|
||||
|
||||
@Singleton
|
||||
class AccountRecommendationsMixerCandidateSource @Inject() (
|
||||
accountRecommendationsMixer: t.AccountRecommendationsMixer.MethodPerEndpoint)
|
||||
extends CandidateSourceWithExtractedFeatures[
|
||||
t.AccountRecommendationsMixerRequest,
|
||||
t.RecommendedUser
|
||||
] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier(name = "AccountRecommendationsMixer")
|
||||
|
||||
override def apply(
|
||||
request: t.AccountRecommendationsMixerRequest
|
||||
): Stitch[CandidatesWithSourceFeatures[t.RecommendedUser]] = {
|
||||
Stitch
|
||||
.callFuture(accountRecommendationsMixer.getWtfRecommendations(request))
|
||||
.map { response: t.WhoToFollowResponse =>
|
||||
responseToCandidatesWithSourceFeatures(
|
||||
response.userRecommendations,
|
||||
response.header,
|
||||
response.footer,
|
||||
response.displayOptions)
|
||||
}
|
||||
}
|
||||
|
||||
private def responseToCandidatesWithSourceFeatures(
|
||||
userRecommendations: Seq[t.RecommendedUser],
|
||||
header: t.Header,
|
||||
footer: Option[t.Footer],
|
||||
displayOptions: Option[t.DisplayOptions],
|
||||
): CandidatesWithSourceFeatures[t.RecommendedUser] = {
|
||||
val features = FeatureMapBuilder()
|
||||
.add(WhoToFollowModuleHeaderFeature, header)
|
||||
.add(WhoToFollowModuleFooterFeature, footer)
|
||||
.add(WhoToFollowModuleDisplayOptionsFeature, displayOptions)
|
||||
.build()
|
||||
CandidatesWithSourceFeatures(userRecommendations, features)
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"account-recommendations-mixer/thrift/src/main/thrift:thrift-scala",
|
||||
"finatra/inject/inject-core/src/main/scala/com/twitter/inject",
|
||||
"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/candidate_source",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline/pipeline_failure",
|
||||
"src/thrift/com/twitter/ads/adserver:adserver_common-scala",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
exports = [
|
||||
"account-recommendations-mixer/thrift/src/main/thrift:thrift-scala",
|
||||
"finatra/inject/inject-core/src/main/scala/com/twitter/inject",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
|
@ -1,29 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.ads
|
||||
|
||||
import com.twitter.adserver.thriftscala.AdImpression
|
||||
import com.twitter.adserver.thriftscala.AdRequestParams
|
||||
import com.twitter.adserver.thriftscala.AdRequestResponse
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.strato.StratoKeyFetcherSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.strato.client.Fetcher
|
||||
import com.twitter.strato.generated.client.ads.admixer.MakeAdRequestClientColumn
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AdsProdStratoCandidateSource @Inject() (adsClient: MakeAdRequestClientColumn)
|
||||
extends StratoKeyFetcherSource[
|
||||
AdRequestParams,
|
||||
AdRequestResponse,
|
||||
AdImpression
|
||||
] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier("AdsProdStrato")
|
||||
|
||||
override val fetcher: Fetcher[AdRequestParams, Unit, AdRequestResponse] = adsClient.fetcher
|
||||
|
||||
override protected def stratoResultTransformer(
|
||||
stratoResult: AdRequestResponse
|
||||
): Seq[AdImpression] =
|
||||
stratoResult.impressions
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.ads
|
||||
|
||||
import com.twitter.adserver.thriftscala.AdImpression
|
||||
import com.twitter.adserver.thriftscala.AdRequestParams
|
||||
import com.twitter.adserver.thriftscala.NewAdServer
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.stitch.Stitch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AdsProdThriftCandidateSource @Inject() (
|
||||
adServerClient: NewAdServer.MethodPerEndpoint)
|
||||
extends CandidateSource[AdRequestParams, AdImpression] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier("AdsProdThrift")
|
||||
|
||||
override def apply(request: AdRequestParams): Stitch[Seq[AdImpression]] =
|
||||
Stitch.callFuture(adServerClient.makeAdRequest(request)).map(_.impressions)
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.ads
|
||||
|
||||
import com.twitter.adserver.thriftscala.AdImpression
|
||||
import com.twitter.adserver.thriftscala.AdRequestParams
|
||||
import com.twitter.adserver.thriftscala.AdRequestResponse
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.strato.StratoKeyFetcherSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.strato.client.Fetcher
|
||||
import com.twitter.strato.generated.client.ads.admixer.MakeAdRequestStagingClientColumn
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AdsStagingCandidateSource @Inject() (adsClient: MakeAdRequestStagingClientColumn)
|
||||
extends StratoKeyFetcherSource[
|
||||
AdRequestParams,
|
||||
AdRequestResponse,
|
||||
AdImpression
|
||||
] {
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier("AdsStaging")
|
||||
|
||||
override val fetcher: Fetcher[AdRequestParams, Unit, AdRequestResponse] = adsClient.fetcher
|
||||
|
||||
override protected def stratoResultTransformer(
|
||||
stratoResult: AdRequestResponse
|
||||
): Seq[AdImpression] =
|
||||
stratoResult.impressions
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"src/thrift/com/twitter/ads/adserver:adserver_common-scala",
|
||||
"src/thrift/com/twitter/ads/adserver:adserver_rpc-scala",
|
||||
"strato/config/columns/ads/admixer:admixer-strato-client",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"src/thrift/com/twitter/ads/adserver:adserver_common-scala",
|
||||
"src/thrift/com/twitter/ads/adserver:adserver_rpc-scala",
|
||||
],
|
||||
)
|
|
@ -1,43 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.ann
|
||||
|
||||
import com.twitter.ann.common._
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.util.{Time => _, _}
|
||||
import com.twitter.finagle.util.DefaultTimer
|
||||
|
||||
/**
|
||||
* @param annQueryableById Ann Queryable by Id client that returns nearest neighbors for a sequence of queries
|
||||
* @param identifier Candidate Source Identifier
|
||||
* @tparam T1 type of the query.
|
||||
* @tparam T2 type of the result.
|
||||
* @tparam P runtime parameters supported by the index.
|
||||
* @tparam D distance function used in the index.
|
||||
*/
|
||||
class AnnCandidateSource[T1, T2, P <: RuntimeParams, D <: Distance[D]](
|
||||
val annQueryableById: QueryableById[T1, T2, P, D],
|
||||
val batchSize: Int,
|
||||
val timeoutPerRequest: Duration,
|
||||
override val identifier: CandidateSourceIdentifier)
|
||||
extends CandidateSource[AnnIdQuery[T1, P], NeighborWithDistanceWithSeed[T1, T2, D]] {
|
||||
|
||||
implicit val timer = DefaultTimer
|
||||
|
||||
override def apply(
|
||||
request: AnnIdQuery[T1, P]
|
||||
): Stitch[Seq[NeighborWithDistanceWithSeed[T1, T2, D]]] = {
|
||||
val ids = request.ids
|
||||
val numOfNeighbors = request.numOfNeighbors
|
||||
val runtimeParams = request.runtimeParams
|
||||
Stitch
|
||||
.collect(
|
||||
ids
|
||||
.grouped(batchSize).map { batchedIds =>
|
||||
annQueryableById
|
||||
.batchQueryWithDistanceById(batchedIds, numOfNeighbors, runtimeParams).map {
|
||||
annResult => annResult.toSeq
|
||||
}.within(timeoutPerRequest).handle { case _ => Seq.empty }
|
||||
}.toSeq).map(_.flatten)
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.ann
|
||||
|
||||
import com.twitter.ann.common._
|
||||
|
||||
/**
|
||||
* A [[AnnIdQuery]] is a query class which defines the ann entities with runtime params and number of neighbors requested
|
||||
*
|
||||
* @param ids Sequence of queries
|
||||
* @param numOfNeighbors Number of neighbors requested
|
||||
* @param runtimeParams ANN Runtime Params
|
||||
* @param batchSize Batch size to the stitch client
|
||||
* @tparam T type of query.
|
||||
* @tparam P runtime parameters supported by the index.
|
||||
*/
|
||||
case class AnnIdQuery[T, P <: RuntimeParams](
|
||||
ids: Seq[T],
|
||||
numOfNeighbors: Int,
|
||||
runtimeParams: P)
|
|
@ -1,17 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"ann/src/main/scala/com/twitter/ann/common",
|
||||
"ann/src/main/scala/com/twitter/ann/hnsw",
|
||||
"ann/src/main/thrift/com/twitter/ann/common:ann-common-scala",
|
||||
"product-mixer/component-library/src/main/thrift/com/twitter/product_mixer/component_library:thrift-scala",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"servo/manhattan/src/main/scala",
|
||||
"servo/repo/src/main/scala",
|
||||
"servo/util/src/main/scala",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
|
@ -1,14 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/cursor",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"src/thrift/com/twitter/periscope/audio_space:audio_space-scala",
|
||||
"strato/config/columns/periscope:periscope-strato-client",
|
||||
"strato/config/src/thrift/com/twitter/strato/graphql:graphql-scala",
|
||||
"strato/src/main/scala/com/twitter/strato/client",
|
||||
],
|
||||
)
|
|
@ -1,49 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.audiospace
|
||||
|
||||
import com.twitter.periscope.audio_space.thriftscala.CreatedSpacesView
|
||||
import com.twitter.periscope.audio_space.thriftscala.SpaceSlice
|
||||
import com.twitter.product_mixer.component_library.model.cursor.NextCursorFeature
|
||||
import com.twitter.product_mixer.component_library.model.cursor.PreviousCursorFeature
|
||||
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.strato.StratoKeyViewFetcherWithSourceFeaturesSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.strato.client.Fetcher
|
||||
import com.twitter.strato.generated.client.periscope.CreatedSpacesSliceOnUserClientColumn
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class CreatedSpacesCandidateSource @Inject() (
|
||||
column: CreatedSpacesSliceOnUserClientColumn)
|
||||
extends StratoKeyViewFetcherWithSourceFeaturesSource[
|
||||
Long,
|
||||
CreatedSpacesView,
|
||||
SpaceSlice,
|
||||
String
|
||||
] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier("CreatedSpaces")
|
||||
|
||||
override val fetcher: Fetcher[Long, CreatedSpacesView, SpaceSlice] = column.fetcher
|
||||
|
||||
override def stratoResultTransformer(
|
||||
stratoKey: Long,
|
||||
stratoResult: SpaceSlice
|
||||
): Seq[String] =
|
||||
stratoResult.items
|
||||
|
||||
override protected def extractFeaturesFromStratoResult(
|
||||
stratoKey: Long,
|
||||
stratoResult: SpaceSlice
|
||||
): FeatureMap = {
|
||||
val featureMapBuilder = FeatureMapBuilder()
|
||||
stratoResult.sliceInfo.previousCursor.foreach { cursor =>
|
||||
featureMapBuilder.add(PreviousCursorFeature, cursor)
|
||||
}
|
||||
stratoResult.sliceInfo.nextCursor.foreach { cursor =>
|
||||
featureMapBuilder.add(NextCursorFeature, cursor)
|
||||
}
|
||||
featureMapBuilder.build()
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/cursor",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"strato/config/columns/consumer-identity/business-profiles:business-profiles-strato-client",
|
||||
"strato/config/src/thrift/com/twitter/strato/graphql:graphql-scala",
|
||||
"strato/src/main/scala/com/twitter/strato/client",
|
||||
],
|
||||
)
|
|
@ -1,53 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.business_profiles
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.cursor.NextCursorFeature
|
||||
import com.twitter.product_mixer.component_library.model.cursor.PreviousCursorFeature
|
||||
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.strato.StratoKeyViewFetcherWithSourceFeaturesSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.strato.client.Fetcher
|
||||
import com.twitter.strato.generated.client.consumer_identity.business_profiles.BusinessProfileTeamMembersOnUserClientColumn
|
||||
import com.twitter.strato.generated.client.consumer_identity.business_profiles.BusinessProfileTeamMembersOnUserClientColumn.{
|
||||
Value => TeamMembersSlice
|
||||
}
|
||||
import com.twitter.strato.generated.client.consumer_identity.business_profiles.BusinessProfileTeamMembersOnUserClientColumn.{
|
||||
View => TeamMembersView
|
||||
}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class TeamMembersCandidateSource @Inject() (
|
||||
column: BusinessProfileTeamMembersOnUserClientColumn)
|
||||
extends StratoKeyViewFetcherWithSourceFeaturesSource[
|
||||
Long,
|
||||
TeamMembersView,
|
||||
TeamMembersSlice,
|
||||
Long
|
||||
] {
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier(
|
||||
"BusinessProfileTeamMembers")
|
||||
|
||||
override val fetcher: Fetcher[Long, TeamMembersView, TeamMembersSlice] = column.fetcher
|
||||
|
||||
override def stratoResultTransformer(
|
||||
stratoKey: Long,
|
||||
stratoResult: TeamMembersSlice
|
||||
): Seq[Long] =
|
||||
stratoResult.members
|
||||
|
||||
override protected def extractFeaturesFromStratoResult(
|
||||
stratoKey: Long,
|
||||
stratoResult: TeamMembersSlice
|
||||
): FeatureMap = {
|
||||
val featureMapBuilder = FeatureMapBuilder()
|
||||
stratoResult.previousCursor.foreach { cursor =>
|
||||
featureMapBuilder.add(PreviousCursorFeature, cursor.toString)
|
||||
}
|
||||
stratoResult.nextCursor.foreach { cursor =>
|
||||
featureMapBuilder.add(NextCursorFeature, cursor.toString)
|
||||
}
|
||||
featureMapBuilder.build()
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"cr-mixer/thrift/src/main/thrift:thrift-scala",
|
||||
"finatra/inject/inject-core/src/main/scala/com/twitter/inject",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
|
@ -1,25 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.cr_mixer
|
||||
|
||||
import com.twitter.cr_mixer.{thriftscala => t}
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.stitch.Stitch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Returns out-of-network Tweet recommendations by using user recommendations
|
||||
* from FollowRecommendationService as an input seed-set to Earlybird
|
||||
*/
|
||||
@Singleton
|
||||
class CrMixerFrsBasedTweetRecommendationsCandidateSource @Inject() (
|
||||
crMixerClient: t.CrMixer.MethodPerEndpoint)
|
||||
extends CandidateSource[t.FrsTweetRequest, t.FrsTweet] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier("CrMixerFrsBasedTweetRecommendations")
|
||||
|
||||
override def apply(request: t.FrsTweetRequest): Stitch[Seq[t.FrsTweet]] = Stitch
|
||||
.callFuture(crMixerClient.getFrsBasedTweetRecommendations(request))
|
||||
.map(_.tweets)
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.cr_mixer
|
||||
|
||||
import com.twitter.cr_mixer.{thriftscala => t}
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.stitch.Stitch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class CrMixerTweetRecommendationsCandidateSource @Inject() (
|
||||
crMixerClient: t.CrMixer.MethodPerEndpoint)
|
||||
extends CandidateSource[t.CrMixerTweetRequest, t.TweetRecommendation] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier("CrMixerTweetRecommendations")
|
||||
|
||||
override def apply(request: t.CrMixerTweetRequest): Stitch[Seq[t.TweetRecommendation]] = Stitch
|
||||
.callFuture(crMixerClient.getTweetRecommendations(request))
|
||||
.map(_.tweets)
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"finatra/inject/inject-core/src/main/scala/com/twitter/inject",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"src/thrift/com/twitter/search:earlybird-scala",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
|
@ -1,26 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.earlybird
|
||||
|
||||
import com.twitter.search.earlybird.{thriftscala => t}
|
||||
import com.twitter.inject.Logging
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.stitch.Stitch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class EarlybirdTweetCandidateSource @Inject() (
|
||||
earlybirdService: t.EarlybirdService.MethodPerEndpoint)
|
||||
extends CandidateSource[t.EarlybirdRequest, t.ThriftSearchResult]
|
||||
with Logging {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier("EarlybirdTweets")
|
||||
|
||||
override def apply(request: t.EarlybirdRequest): Stitch[Seq[t.ThriftSearchResult]] = {
|
||||
Stitch
|
||||
.callFuture(earlybirdService.search(request))
|
||||
.map { response: t.EarlybirdResponse =>
|
||||
response.searchResults.map(_.results).getOrElse(Seq.empty)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"explore/explore-ranker/thrift/src/main/thrift:thrift-scala",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
|
@ -1,31 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.explore_ranker
|
||||
|
||||
import com.twitter.explore_ranker.{thriftscala => t}
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.stitch.Stitch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ExploreRankerCandidateSource @Inject() (
|
||||
exploreRankerService: t.ExploreRanker.MethodPerEndpoint)
|
||||
extends CandidateSource[t.ExploreRankerRequest, t.ImmersiveRecsResult] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier("ExploreRanker")
|
||||
|
||||
override def apply(
|
||||
request: t.ExploreRankerRequest
|
||||
): Stitch[Seq[t.ImmersiveRecsResult]] = {
|
||||
Stitch
|
||||
.callFuture(exploreRankerService.getRankedResults(request))
|
||||
.map {
|
||||
case t.ExploreRankerResponse(
|
||||
t.ExploreRankerProductResponse
|
||||
.ImmersiveRecsResponse(t.ImmersiveRecsResponse(immersiveRecsResults))) =>
|
||||
immersiveRecsResults
|
||||
case response =>
|
||||
throw new UnsupportedOperationException(s"Unknown response type: $response")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"finatra/inject/inject-core/src/main/scala/com/twitter/inject",
|
||||
"onboarding/service/thrift/src/main/thrift:thrift-scala",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
exports = [
|
||||
"onboarding/service/thrift/src/main/thrift:thrift-scala",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
],
|
||||
)
|
|
@ -1,50 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.flexible_injection_pipeline
|
||||
|
||||
import com.twitter.inject.Logging
|
||||
import com.twitter.onboarding.injections.{thriftscala => injectionsthrift}
|
||||
import com.twitter.onboarding.task.service.{thriftscala => servicethrift}
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.stitch.Stitch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Returns a list of prompts to insert into a user's timeline (inline prompt, cover modals, etc)
|
||||
* from go/flip (the prompting platform for Twitter).
|
||||
*/
|
||||
@Singleton
|
||||
class PromptCandidateSource @Inject() (taskService: servicethrift.TaskService.MethodPerEndpoint)
|
||||
extends CandidateSource[servicethrift.GetInjectionsRequest, IntermediatePrompt]
|
||||
with Logging {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier(
|
||||
"InjectionPipelinePrompts")
|
||||
|
||||
override def apply(
|
||||
request: servicethrift.GetInjectionsRequest
|
||||
): Stitch[Seq[IntermediatePrompt]] = {
|
||||
Stitch
|
||||
.callFuture(taskService.getInjections(request)).map {
|
||||
_.injections.flatMap {
|
||||
// The entire carousel is getting added to each IntermediatePrompt item with a
|
||||
// corresponding index to be unpacked later on to populate its TimelineEntry counterpart.
|
||||
case injection: injectionsthrift.Injection.TilesCarousel =>
|
||||
injection.tilesCarousel.tiles.zipWithIndex.map {
|
||||
case (tile: injectionsthrift.Tile, index: Int) =>
|
||||
IntermediatePrompt(injection, Some(index), Some(tile))
|
||||
}
|
||||
case injection => Seq(IntermediatePrompt(injection, None, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives an intermediate step to help 'explosion' of tile carousel tiles due to TimelineModule
|
||||
* not being an extension of TimelineItem
|
||||
*/
|
||||
case class IntermediatePrompt(
|
||||
injection: injectionsthrift.Injection,
|
||||
offsetInModule: Option[Int],
|
||||
carouselTile: Option[injectionsthrift.Tile])
|
|
@ -1,16 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"src/thrift/com/twitter/hermit:hermit-scala",
|
||||
"strato/config/columns/onboarding:onboarding-strato-client",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"src/thrift/com/twitter/hermit:hermit-scala",
|
||||
],
|
||||
)
|
|
@ -1,32 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.hermit
|
||||
|
||||
import com.twitter.hermit.thriftscala.RecommendationRequest
|
||||
import com.twitter.hermit.thriftscala.RecommendationResponse
|
||||
import com.twitter.hermit.thriftscala.RelatedUser
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.strato.StratoKeyViewFetcherSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.strato.client.Fetcher
|
||||
import com.twitter.strato.generated.client.onboarding.HermitRecommendUsersClientColumn
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class UsersSimilarToMeCandidateSource @Inject() (
|
||||
column: HermitRecommendUsersClientColumn)
|
||||
extends StratoKeyViewFetcherSource[
|
||||
Long,
|
||||
RecommendationRequest,
|
||||
RecommendationResponse,
|
||||
RelatedUser
|
||||
] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier("UsersSimilarToMe")
|
||||
|
||||
override val fetcher: Fetcher[Long, RecommendationRequest, RecommendationResponse] =
|
||||
column.fetcher
|
||||
|
||||
override def stratoResultTransformer(
|
||||
stratoKey: Long,
|
||||
result: RecommendationResponse
|
||||
): Seq[RelatedUser] = result.suggestions.getOrElse(Seq.empty).filter(_.id.isDefined)
|
||||
}
|
|
@ -1,18 +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/com/twitter/inject",
|
||||
"interests-service/thrift/src/main/thrift:thrift-scala",
|
||||
"interests_discovery/thrift/src/main/thrift:service-thrift-scala",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
exports = [
|
||||
"interests-service/thrift/src/main/thrift:thrift-scala",
|
||||
"interests_discovery/thrift/src/main/thrift:service-thrift-scala",
|
||||
],
|
||||
)
|
|
@ -1,34 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.interest_discovery
|
||||
|
||||
import com.google.inject.Inject
|
||||
import com.google.inject.Singleton
|
||||
import com.twitter.inject.Logging
|
||||
import com.twitter.interests_discovery.{thriftscala => t}
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* Generate a list of related topics results from IDS getRelatedTopics (thrift) endpoint.
|
||||
* Returns related topics, given a topic, whereas [[RecommendedTopicsCandidateSource]] returns
|
||||
* recommended topics, given a user.
|
||||
*/
|
||||
@Singleton
|
||||
class RelatedTopicsCandidateSource @Inject() (
|
||||
interestDiscoveryService: t.InterestsDiscoveryService.MethodPerEndpoint)
|
||||
extends CandidateSource[t.RelatedTopicsRequest, t.RelatedTopic]
|
||||
with Logging {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier(name = "RelatedTopics")
|
||||
|
||||
override def apply(
|
||||
request: t.RelatedTopicsRequest
|
||||
): Stitch[Seq[t.RelatedTopic]] = {
|
||||
Stitch
|
||||
.callFuture(interestDiscoveryService.getRelatedTopics(request))
|
||||
.map { response: t.RelatedTopicsResponse =>
|
||||
response.topics
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"interests_discovery/thrift/src/main/thrift:service-thrift-scala",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"strato/config/columns/recommendations/interests_discovery/recommendations_mh:recommendations_mh-strato-client",
|
||||
],
|
||||
exports = [
|
||||
"strato/config/columns/recommendations/interests_discovery/recommendations_mh:recommendations_mh-strato-client",
|
||||
],
|
||||
)
|
|
@ -1,39 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.lists
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TwitterListCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.strato.StratoKeyFetcherSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.strato.client.Fetcher
|
||||
import com.twitter.strato.generated.client.recommendations.interests_discovery.recommendations_mh.OrganicPopgeoListsClientColumn
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class OrganicPopGeoListsCandidateSource @Inject() (
|
||||
organicPopgeoListsClientColumn: OrganicPopgeoListsClientColumn)
|
||||
extends StratoKeyFetcherSource[
|
||||
OrganicPopgeoListsClientColumn.Key,
|
||||
OrganicPopgeoListsClientColumn.Value,
|
||||
TwitterListCandidate
|
||||
] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier(
|
||||
"OrganicPopGeoLists")
|
||||
|
||||
override val fetcher: Fetcher[
|
||||
OrganicPopgeoListsClientColumn.Key,
|
||||
Unit,
|
||||
OrganicPopgeoListsClientColumn.Value
|
||||
] =
|
||||
organicPopgeoListsClientColumn.fetcher
|
||||
|
||||
override def stratoResultTransformer(
|
||||
stratoResult: OrganicPopgeoListsClientColumn.Value
|
||||
): Seq[TwitterListCandidate] = {
|
||||
stratoResult.recommendedListsByAlgo.flatMap { topLists =>
|
||||
topLists.lists.map { list =>
|
||||
TwitterListCandidate(list.listId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"finatra/inject/inject-core/src/main/scala/com/twitter/inject",
|
||||
"people-discovery/api/thrift:thrift-scala",
|
||||
"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/candidate_source",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline/pipeline_failure",
|
||||
"src/thrift/com/twitter/ads/adserver:adserver_common-scala",
|
||||
"src/thrift/com/twitter/hermit:hermit-scala",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
exports = [
|
||||
"finatra/inject/inject-core/src/main/scala/com/twitter/inject",
|
||||
"people-discovery/api/thrift:thrift-scala",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
|
@ -1,71 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.people_discovery
|
||||
|
||||
import com.twitter.peoplediscovery.api.{thriftscala => t}
|
||||
import com.twitter.product_mixer.component_library.model.candidate.UserCandidate
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSourceWithExtractedFeatures
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidatesWithSourceFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.PipelineFailure
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.UnexpectedCandidateResult
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.util.logging.Logging
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
object WhoToFollowModuleHeaderFeature extends Feature[UserCandidate, t.Header]
|
||||
object WhoToFollowModuleDisplayOptionsFeature
|
||||
extends Feature[UserCandidate, Option[t.DisplayOptions]]
|
||||
object WhoToFollowModuleShowMoreFeature extends Feature[UserCandidate, Option[t.ShowMore]]
|
||||
|
||||
@Singleton
|
||||
class PeopleDiscoveryCandidateSource @Inject() (
|
||||
peopleDiscoveryService: t.ThriftPeopleDiscoveryService.MethodPerEndpoint)
|
||||
extends CandidateSourceWithExtractedFeatures[t.GetModuleRequest, t.RecommendedUser]
|
||||
with Logging {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier(name = "PeopleDiscovery")
|
||||
|
||||
override def apply(
|
||||
request: t.GetModuleRequest
|
||||
): Stitch[CandidatesWithSourceFeatures[t.RecommendedUser]] = {
|
||||
Stitch
|
||||
.callFuture(peopleDiscoveryService.getModules(request))
|
||||
.map { response: t.GetModuleResponse =>
|
||||
// under the assumption getModules returns a maximum of one module
|
||||
response.modules
|
||||
.collectFirst { module =>
|
||||
module.layout match {
|
||||
case t.Layout.UserBioList(layout) =>
|
||||
layoutToCandidatesWithSourceFeatures(
|
||||
layout.userRecommendations,
|
||||
layout.header,
|
||||
layout.displayOptions,
|
||||
layout.showMore)
|
||||
case t.Layout.UserTweetCarousel(layout) =>
|
||||
layoutToCandidatesWithSourceFeatures(
|
||||
layout.userRecommendations,
|
||||
layout.header,
|
||||
layout.displayOptions,
|
||||
layout.showMore)
|
||||
}
|
||||
}.getOrElse(throw PipelineFailure(UnexpectedCandidateResult, "unexpected missing module"))
|
||||
}
|
||||
}
|
||||
|
||||
private def layoutToCandidatesWithSourceFeatures(
|
||||
userRecommendations: Seq[t.RecommendedUser],
|
||||
header: t.Header,
|
||||
displayOptions: Option[t.DisplayOptions],
|
||||
showMore: Option[t.ShowMore],
|
||||
): CandidatesWithSourceFeatures[t.RecommendedUser] = {
|
||||
val features = FeatureMapBuilder()
|
||||
.add(WhoToFollowModuleHeaderFeature, header)
|
||||
.add(WhoToFollowModuleDisplayOptionsFeature, displayOptions)
|
||||
.add(WhoToFollowModuleShowMoreFeature, showMore)
|
||||
.build()
|
||||
CandidatesWithSourceFeatures(userRecommendations, features)
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"follow-recommendations-service/thrift/src/main/thrift:thrift-scala",
|
||||
"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/candidate_source",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"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/pipeline/candidate",
|
||||
"strato/config/columns/onboarding/follow-recommendations-service:follow-recommendations-service-strato-client",
|
||||
],
|
||||
exports = [
|
||||
"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/model/marshalling/request",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline/candidate",
|
||||
],
|
||||
)
|
|
@ -1,41 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.recommendations
|
||||
|
||||
import com.twitter.follow_recommendations.{thriftscala => fr}
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.strato.StratoKeyViewFetcherSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.strato.client.Fetcher
|
||||
import com.twitter.strato.generated.client.onboarding.follow_recommendations_service.GetRecommendationsClientColumn
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Returns a list of FollowRecommendations as [[fr.UserRecommendation]]s fetched from Strato
|
||||
*/
|
||||
@Singleton
|
||||
class UserFollowRecommendationsCandidateSource @Inject() (
|
||||
getRecommendationsClientColumn: GetRecommendationsClientColumn)
|
||||
extends StratoKeyViewFetcherSource[
|
||||
fr.RecommendationRequest,
|
||||
Unit,
|
||||
fr.RecommendationResponse,
|
||||
fr.UserRecommendation
|
||||
] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier(
|
||||
"FollowRecommendationsService")
|
||||
|
||||
override val fetcher: Fetcher[fr.RecommendationRequest, Unit, fr.RecommendationResponse] =
|
||||
getRecommendationsClientColumn.fetcher
|
||||
|
||||
override def stratoResultTransformer(
|
||||
stratoKey: fr.RecommendationRequest,
|
||||
stratoResult: fr.RecommendationResponse
|
||||
): Seq[fr.UserRecommendation] = {
|
||||
stratoResult.recommendations.map {
|
||||
case fr.Recommendation.User(userRec: fr.UserRecommendation) =>
|
||||
userRec
|
||||
case _ =>
|
||||
throw new Exception("Invalid recommendation type returned from FRS")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"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/candidate_source",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"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/pipeline/candidate",
|
||||
"socialgraph/server/src/main/scala/com/twitter/socialgraph/util",
|
||||
"src/thrift/com/twitter/socialgraph:thrift-scala",
|
||||
],
|
||||
exports = [
|
||||
"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/candidate_source",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"src/thrift/com/twitter/socialgraph:thrift-scala",
|
||||
],
|
||||
)
|
|
@ -1,57 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.social_graph
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.CursorType
|
||||
import com.twitter.product_mixer.component_library.model.candidate.NextCursor
|
||||
import com.twitter.product_mixer.component_library.model.candidate.PreviousCursor
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.strato.StratoKeyViewFetcherSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.socialgraph.thriftscala
|
||||
import com.twitter.socialgraph.thriftscala.IdsRequest
|
||||
import com.twitter.socialgraph.thriftscala.IdsResult
|
||||
import com.twitter.socialgraph.util.ByteBufferUtil
|
||||
import com.twitter.strato.client.Fetcher
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
sealed trait SocialgraphResponse
|
||||
case class SocialgraphResult(id: Long) extends SocialgraphResponse
|
||||
case class SocialgraphCursor(cursor: Long, cursorType: CursorType) extends SocialgraphResponse
|
||||
|
||||
@Singleton
|
||||
class SocialgraphCandidateSource @Inject() (
|
||||
override val fetcher: Fetcher[thriftscala.IdsRequest, Option[
|
||||
thriftscala.RequestContext
|
||||
], thriftscala.IdsResult])
|
||||
extends StratoKeyViewFetcherSource[
|
||||
thriftscala.IdsRequest,
|
||||
Option[thriftscala.RequestContext],
|
||||
thriftscala.IdsResult,
|
||||
SocialgraphResponse
|
||||
] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier("Socialgraph")
|
||||
|
||||
override def stratoResultTransformer(
|
||||
stratoKey: IdsRequest,
|
||||
stratoResult: IdsResult
|
||||
): Seq[SocialgraphResponse] = {
|
||||
val prevCursor =
|
||||
SocialgraphCursor(ByteBufferUtil.toLong(stratoResult.pageResult.prevCursor), PreviousCursor)
|
||||
/* When an end cursor is passed to Socialgraph,
|
||||
* Socialgraph returns the start cursor. To prevent
|
||||
* clients from circularly fetching the timeline again,
|
||||
* if we see a start cursor returned from Socialgraph,
|
||||
* we replace it with an end cursor.
|
||||
*/
|
||||
val nextCursor = ByteBufferUtil.toLong(stratoResult.pageResult.nextCursor) match {
|
||||
case SocialgraphCursorConstants.StartCursor =>
|
||||
SocialgraphCursor(SocialgraphCursorConstants.EndCursor, NextCursor)
|
||||
case cursor => SocialgraphCursor(cursor, NextCursor)
|
||||
}
|
||||
|
||||
stratoResult.ids
|
||||
.map { id =>
|
||||
SocialgraphResult(id)
|
||||
} ++ Seq(nextCursor, prevCursor)
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.social_graph
|
||||
|
||||
object SocialgraphCursorConstants {
|
||||
val EndCursor: Long = 0L
|
||||
val StartCursor: Long = -1L
|
||||
val LastSortIndex: Long = 0L
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"finatra/inject/inject-core/src/main/scala/com/twitter/inject",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"src/thrift/com/twitter/timelineranker:thrift-scala",
|
||||
"src/thrift/com/twitter/timelineranker/server/model:thrift-scala",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
|
@ -1,51 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.timeline_ranker
|
||||
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSourceWithExtractedFeatures
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidatesWithSourceFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.timelineranker.{thriftscala => t}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Map of tweetId -> sourceTweet of retweets present in Timeline Ranker candidates list.
|
||||
* These tweets are used only for further ranking. They are not returned to the end user.
|
||||
*/
|
||||
object TimelineRankerInNetworkSourceTweetsByTweetIdMapFeature
|
||||
extends Feature[PipelineQuery, Map[Long, t.CandidateTweet]]
|
||||
|
||||
@Singleton
|
||||
class TimelineRankerInNetworkCandidateSource @Inject() (
|
||||
timelineRankerClient: t.TimelineRanker.MethodPerEndpoint)
|
||||
extends CandidateSourceWithExtractedFeatures[t.RecapQuery, t.CandidateTweet] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier("TimelineRankerInNetwork")
|
||||
|
||||
override def apply(
|
||||
request: t.RecapQuery
|
||||
): Stitch[CandidatesWithSourceFeatures[t.CandidateTweet]] = {
|
||||
Stitch
|
||||
.callFuture(timelineRankerClient.getRecycledTweetCandidates(Seq(request)))
|
||||
.map { response: Seq[t.GetCandidateTweetsResponse] =>
|
||||
val candidates =
|
||||
response.headOption.flatMap(_.candidates).getOrElse(Seq.empty).filter(_.tweet.nonEmpty)
|
||||
val sourceTweetsByTweetId =
|
||||
response.headOption
|
||||
.flatMap(_.sourceTweets).getOrElse(Seq.empty).filter(_.tweet.nonEmpty)
|
||||
.map { candidate =>
|
||||
(candidate.tweet.get.id, candidate)
|
||||
}.toMap
|
||||
val sourceTweetsByTweetIdMapFeature = FeatureMapBuilder()
|
||||
.add(TimelineRankerInNetworkSourceTweetsByTweetIdMapFeature, sourceTweetsByTweetId)
|
||||
.build()
|
||||
CandidatesWithSourceFeatures(
|
||||
candidates = candidates,
|
||||
features = sourceTweetsByTweetIdMapFeature)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.timeline_ranker
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.timelineranker.{thriftscala => t}
|
||||
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class TimelineRankerRecapCandidateSource @Inject() (
|
||||
timelineRankerClient: t.TimelineRanker.MethodPerEndpoint)
|
||||
extends CandidateSource[t.RecapQuery, t.CandidateTweet] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier("TimelineRankerRecap")
|
||||
|
||||
override def apply(
|
||||
request: t.RecapQuery
|
||||
): Stitch[Seq[t.CandidateTweet]] = {
|
||||
Stitch
|
||||
.callFuture(timelineRankerClient.getRecapCandidatesFromAuthors(Seq(request)))
|
||||
.map { response: Seq[t.GetCandidateTweetsResponse] =>
|
||||
response.headOption.flatMap(_.candidates).getOrElse(Seq.empty).filter(_.tweet.nonEmpty)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.timeline_ranker
|
||||
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSourceWithExtractedFeatures
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidatesWithSourceFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.PipelineFailure
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.UnexpectedCandidateResult
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.timelineranker.{thriftscala => t}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Source tweets of retweets present in Timeline Ranker candidates list.
|
||||
* These tweets are used only for further ranking. They are not returned to the end user.
|
||||
*/
|
||||
case object TimelineRankerUtegSourceTweetsFeature
|
||||
extends Feature[PipelineQuery, Seq[t.CandidateTweet]]
|
||||
|
||||
@Singleton
|
||||
class TimelineRankerUtegCandidateSource @Inject() (
|
||||
timelineRankerClient: t.TimelineRanker.MethodPerEndpoint)
|
||||
extends CandidateSourceWithExtractedFeatures[t.UtegLikedByTweetsQuery, t.CandidateTweet] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier("TimelineRankerUteg")
|
||||
|
||||
override def apply(
|
||||
request: t.UtegLikedByTweetsQuery
|
||||
): Stitch[CandidatesWithSourceFeatures[t.CandidateTweet]] = {
|
||||
Stitch
|
||||
.callFuture(timelineRankerClient.getUtegLikedByTweetCandidates(Seq(request)))
|
||||
.map { response =>
|
||||
val result = response.headOption.getOrElse(
|
||||
throw PipelineFailure(UnexpectedCandidateResult, "Empty Timeline Ranker response"))
|
||||
val candidates = result.candidates.toSeq.flatten
|
||||
val sourceTweets = result.sourceTweets.toSeq.flatten
|
||||
|
||||
val candidateSourceFeatures = FeatureMapBuilder()
|
||||
.add(TimelineRankerUtegSourceTweetsFeature, sourceTweets)
|
||||
.build()
|
||||
|
||||
CandidatesWithSourceFeatures(candidates = candidates, features = candidateSourceFeatures)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"finatra/inject/inject-core/src/main/scala/com/twitter/inject",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"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/timelineservice/server/suggests/features:thrift-scala",
|
||||
"src/thrift/com/twitter/timelineservice/server/suggests/logging:thrift-scala",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
|
@ -1,156 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.timeline_scorer
|
||||
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSourceWithExtractedFeatures
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidatesWithSourceFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.timelinescorer.common.scoredtweetcandidate.thriftscala.v1
|
||||
import com.twitter.timelinescorer.common.scoredtweetcandidate.thriftscala.v1.Ancestor
|
||||
import com.twitter.timelinescorer.common.scoredtweetcandidate.{thriftscala => ct}
|
||||
import com.twitter.timelinescorer.{thriftscala => t}
|
||||
import com.twitter.timelineservice.suggests.logging.candidate_tweet_source_id.thriftscala.CandidateTweetSourceId
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
case class ScoredTweetCandidateWithFocalTweet(
|
||||
candidate: v1.ScoredTweetCandidate,
|
||||
focalTweetIdOpt: Option[Long])
|
||||
|
||||
case object TimelineScorerCandidateSourceSucceededFeature extends Feature[PipelineQuery, Boolean]
|
||||
|
||||
@Singleton
|
||||
class TimelineScorerCandidateSource @Inject() (
|
||||
timelineScorerClient: t.TimelineScorer.MethodPerEndpoint)
|
||||
extends CandidateSourceWithExtractedFeatures[
|
||||
t.ScoredTweetsRequest,
|
||||
ScoredTweetCandidateWithFocalTweet
|
||||
] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier("TimelineScorer")
|
||||
|
||||
private val MaxConversationAncestors = 2
|
||||
|
||||
override def apply(
|
||||
request: t.ScoredTweetsRequest
|
||||
): Stitch[CandidatesWithSourceFeatures[ScoredTweetCandidateWithFocalTweet]] = {
|
||||
Stitch
|
||||
.callFuture(timelineScorerClient.getScoredTweets(request))
|
||||
.map { response =>
|
||||
val scoredTweetsOpt = response match {
|
||||
case t.ScoredTweetsResponse.V1(v1) => v1.scoredTweets
|
||||
case t.ScoredTweetsResponse.UnknownUnionField(field) =>
|
||||
throw new UnsupportedOperationException(s"Unknown response type: ${field.field.name}")
|
||||
}
|
||||
val scoredTweets = scoredTweetsOpt.getOrElse(Seq.empty)
|
||||
|
||||
val allAncestors = scoredTweets.flatMap {
|
||||
case ct.ScoredTweetCandidate.V1(v1) if isEligibleReply(v1) =>
|
||||
v1.ancestors.get.map(_.tweetId)
|
||||
case _ => Seq.empty
|
||||
}.toSet
|
||||
|
||||
// Remove tweets within ancestor list of other tweets to avoid serving duplicates
|
||||
val keptTweets = scoredTweets.collect {
|
||||
case ct.ScoredTweetCandidate.V1(v1) if !allAncestors.contains(originalTweetId(v1)) => v1
|
||||
}
|
||||
|
||||
// Add parent and root tweet for eligible reply focal tweets
|
||||
val candidates = keptTweets
|
||||
.flatMap {
|
||||
case v1 if isEligibleReply(v1) =>
|
||||
val ancestors = v1.ancestors.get
|
||||
val focalTweetId = v1.tweetId
|
||||
|
||||
// Include root tweet if the conversation has atleast 2 ancestors
|
||||
val optionallyIncludedRootTweet = if (ancestors.size >= MaxConversationAncestors) {
|
||||
val rootTweet = toScoredTweetCandidateFromAncestor(
|
||||
ancestor = ancestors.last,
|
||||
inReplyToTweetId = None,
|
||||
conversationId = v1.conversationId,
|
||||
ancestors = None,
|
||||
candidateTweetSourceId = v1.candidateTweetSourceId
|
||||
)
|
||||
Seq((rootTweet, Some(v1)))
|
||||
} else Seq.empty
|
||||
|
||||
/**
|
||||
* Setting the in-reply-to tweet id on the immediate parent, if one exists,
|
||||
* helps ensure tweet type metrics correctly distinguish roots from non-roots.
|
||||
*/
|
||||
val inReplyToTweetId = ancestors.tail.headOption.map(_.tweetId)
|
||||
val parentAncestor = toScoredTweetCandidateFromAncestor(
|
||||
ancestor = ancestors.head,
|
||||
inReplyToTweetId = inReplyToTweetId,
|
||||
conversationId = v1.conversationId,
|
||||
ancestors = Some(ancestors.tail),
|
||||
candidateTweetSourceId = v1.candidateTweetSourceId
|
||||
)
|
||||
|
||||
optionallyIncludedRootTweet ++
|
||||
Seq((parentAncestor, Some(v1)), (v1, Some(v1)))
|
||||
|
||||
case any => Seq((any, None)) // Set focalTweetId to None if not eligible for convo
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedup each tweet keeping the one with highest scored Focal Tweet
|
||||
* Focal Tweet ID != the Conversation ID, which is set to the root of the conversation
|
||||
* Focal Tweet ID will be defined for tweets with ancestors that should be
|
||||
* in conversation modules and None for standalone tweets.
|
||||
*/
|
||||
val sortedDedupedCandidates = candidates
|
||||
.groupBy { case (v1, _) => v1.tweetId }
|
||||
.mapValues { group =>
|
||||
val (candidate, focalTweetOpt) = group.maxBy {
|
||||
case (_, Some(focal)) => focal.score
|
||||
case (_, None) => 0
|
||||
}
|
||||
ScoredTweetCandidateWithFocalTweet(candidate, focalTweetOpt.map(focal => focal.tweetId))
|
||||
}.values.toSeq.sortBy(_.candidate.tweetId)
|
||||
|
||||
CandidatesWithSourceFeatures(
|
||||
candidates = sortedDedupedCandidates,
|
||||
features = FeatureMapBuilder()
|
||||
.add(TimelineScorerCandidateSourceSucceededFeature, true)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def isEligibleReply(candidate: ct.ScoredTweetCandidateAliases.V1Alias): Boolean = {
|
||||
candidate.inReplyToTweetId.nonEmpty &&
|
||||
!candidate.isRetweet.getOrElse(false) &&
|
||||
candidate.ancestors.exists(_.nonEmpty)
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have a retweet, get the source tweet id.
|
||||
* If it is not a retweet, get the regular tweet id.
|
||||
*/
|
||||
private def originalTweetId(candidate: ct.ScoredTweetCandidateAliases.V1Alias): Long = {
|
||||
candidate.sourceTweetId.getOrElse(candidate.tweetId)
|
||||
}
|
||||
|
||||
private def toScoredTweetCandidateFromAncestor(
|
||||
ancestor: Ancestor,
|
||||
inReplyToTweetId: Option[Long],
|
||||
conversationId: Option[Long],
|
||||
ancestors: Option[Seq[Ancestor]],
|
||||
candidateTweetSourceId: Option[CandidateTweetSourceId]
|
||||
): ct.ScoredTweetCandidateAliases.V1Alias = {
|
||||
ct.v1.ScoredTweetCandidate(
|
||||
tweetId = ancestor.tweetId,
|
||||
authorId = ancestor.userId.getOrElse(0L),
|
||||
score = 0.0,
|
||||
isAncestorCandidate = Some(true),
|
||||
inReplyToTweetId = inReplyToTweetId,
|
||||
conversationId = conversationId,
|
||||
ancestors = ancestors,
|
||||
candidateTweetSourceId = candidateTweetSourceId
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/cursor",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"src/thrift/com/twitter/timelineservice/server/internal:thrift-scala",
|
||||
"stitch/stitch-timelineservice/src/main/scala",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/cursor",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source",
|
||||
"stitch/stitch-timelineservice/src/main/scala",
|
||||
],
|
||||
)
|
|
@ -1,48 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.timeline_service
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.cursor.NextCursorFeature
|
||||
import com.twitter.product_mixer.component_library.model.cursor.PreviousCursorFeature
|
||||
import com.twitter.product_mixer.core.feature.FeatureWithDefaultOnFailure
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSourceWithExtractedFeatures
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidatesWithSourceFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
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
|
||||
|
||||
case object TimelineServiceResponseWasTruncatedFeature
|
||||
extends FeatureWithDefaultOnFailure[PipelineQuery, Boolean] {
|
||||
override val defaultValue: Boolean = false
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class TimelineServiceTweetCandidateSource @Inject() (
|
||||
timelineService: TimelineService)
|
||||
extends CandidateSourceWithExtractedFeatures[t.TimelineQuery, t.Tweet] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier("TimelineServiceTweet")
|
||||
|
||||
override def apply(request: t.TimelineQuery): Stitch[CandidatesWithSourceFeatures[t.Tweet]] = {
|
||||
timelineService
|
||||
.getTimeline(request).map { timeline =>
|
||||
val candidates = timeline.entries.collect {
|
||||
case t.TimelineEntry.Tweet(tweet) => tweet
|
||||
}
|
||||
|
||||
val candidateSourceFeatures =
|
||||
FeatureMapBuilder()
|
||||
.add(TimelineServiceResponseWasTruncatedFeature, timeline.wasTruncated.getOrElse(false))
|
||||
.add(PreviousCursorFeature, timeline.responseCursor.flatMap(_.top).getOrElse(""))
|
||||
.add(NextCursorFeature, timeline.responseCursor.flatMap(_.bottom).getOrElse(""))
|
||||
.build()
|
||||
|
||||
CandidatesWithSourceFeatures(candidates = candidates, features = candidateSourceFeatures)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"src/thrift/com/twitter/timelines/impression:thrift-scala",
|
||||
"strato/config/columns/timelines/impression-store:impression-store-strato-client",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"strato/config/columns/timelines/impression-store:impression-store-strato-client",
|
||||
],
|
||||
)
|
|
@ -1,30 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.timelines_impression_store
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.strato.StratoKeyFetcherSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.strato.client.Fetcher
|
||||
import com.twitter.strato.generated.client.timelines.impression_store.TweetImpressionStoreManhattanV2OnUserClientColumn
|
||||
import com.twitter.timelines.impression.thriftscala.TweetImpressionsEntries
|
||||
import com.twitter.timelines.impression.{thriftscala => t}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class TimelinesImpressionStoreCandidateSourceV2 @Inject() (
|
||||
client: TweetImpressionStoreManhattanV2OnUserClientColumn)
|
||||
extends StratoKeyFetcherSource[
|
||||
Long,
|
||||
t.TweetImpressionsEntries,
|
||||
t.TweetImpressionsEntry
|
||||
] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier(
|
||||
"TimelinesImpressionStore")
|
||||
|
||||
override val fetcher: Fetcher[Long, Unit, TweetImpressionsEntries] = client.fetcher
|
||||
|
||||
override def stratoResultTransformer(
|
||||
stratoResult: t.TweetImpressionsEntries
|
||||
): Seq[t.TweetImpressionsEntry] =
|
||||
stratoResult.entries
|
||||
}
|
|
@ -1,11 +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/functional_component/candidate_source/strato",
|
||||
"strato/config/columns/interests:interests-strato-client",
|
||||
"strato/src/main/scala/com/twitter/strato/client",
|
||||
],
|
||||
)
|
|
@ -1,21 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.topics
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.strato.StratoKeyViewFetcherSeqSource
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.strato.client.Fetcher
|
||||
import com.twitter.strato.generated.client.interests.FollowedTopicsGetterClientColumn
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FollowedTopicsCandidateSource @Inject() (
|
||||
column: FollowedTopicsGetterClientColumn)
|
||||
extends StratoKeyViewFetcherSeqSource[
|
||||
Long,
|
||||
Unit,
|
||||
Long
|
||||
] {
|
||||
override val identifier: CandidateSourceIdentifier = CandidateSourceIdentifier("FollowedTopics")
|
||||
|
||||
override val fetcher: Fetcher[Long, Unit, Seq[Long]] = column.fetcher
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"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/model/candidate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"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/selector",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/transformer",
|
||||
"src/thrift/com/twitter/timelinescorer/common/scoredtweetcandidate:thrift-scala",
|
||||
"src/thrift/com/twitter/timelineservice/server/internal:thrift-scala",
|
||||
"strato/config/columns/tweetconvosvc:tweetconvosvc-strato-client",
|
||||
"tweetconvosvc/common/src/main/thrift/com/twitter/tweetconvosvc/tweet_ancestor:thrift-scala",
|
||||
"tweetconvosvc/thrift/src/main/thrift:thrift-scala",
|
||||
],
|
||||
exports = [
|
||||
"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/model/candidate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/candidate_source/strato",
|
||||
"strato/config/columns/tweetconvosvc:tweetconvosvc-strato-client",
|
||||
"tweetconvosvc/thrift/src/main/thrift:thrift-scala",
|
||||
],
|
||||
)
|
|
@ -1,173 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.tweetconvosvc
|
||||
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidateSourceWithExtractedFeatures
|
||||
import com.twitter.product_mixer.core.functional_component.candidate_source.CandidatesWithSourceFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.CandidateSourceIdentifier
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetconvosvc.tweet_ancestor.{thriftscala => ta}
|
||||
import com.twitter.tweetconvosvc.{thriftscala => tcs}
|
||||
import com.twitter.util.Return
|
||||
import com.twitter.util.Throw
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
case class ConversationServiceCandidateSourceRequest(
|
||||
tweetsWithConversationMetadata: Seq[TweetWithConversationMetadata])
|
||||
|
||||
case class TweetWithConversationMetadata(
|
||||
tweetId: Long,
|
||||
userId: Option[Long],
|
||||
sourceTweetId: Option[Long],
|
||||
sourceUserId: Option[Long],
|
||||
inReplyToTweetId: Option[Long],
|
||||
conversationId: Option[Long],
|
||||
ancestors: Seq[ta.TweetAncestor])
|
||||
|
||||
/**
|
||||
* Candidate source that fetches ancestors of input candidates from Tweetconvosvc and
|
||||
* returns a flattened list of input and ancestor candidates.
|
||||
*/
|
||||
@Singleton
|
||||
class ConversationServiceCandidateSource @Inject() (
|
||||
conversationServiceClient: tcs.ConversationService.MethodPerEndpoint)
|
||||
extends CandidateSourceWithExtractedFeatures[
|
||||
ConversationServiceCandidateSourceRequest,
|
||||
TweetWithConversationMetadata
|
||||
] {
|
||||
|
||||
override val identifier: CandidateSourceIdentifier =
|
||||
CandidateSourceIdentifier("ConversationService")
|
||||
|
||||
private val maxModuleSize = 3
|
||||
private val maxAncestorsInConversation = 2
|
||||
private val numberOfRootTweets = 1
|
||||
private val maxTweetsInConversationWithSameId = 1
|
||||
|
||||
override def apply(
|
||||
request: ConversationServiceCandidateSourceRequest
|
||||
): Stitch[CandidatesWithSourceFeatures[TweetWithConversationMetadata]] = {
|
||||
val inputTweetsWithConversationMetadata: Seq[TweetWithConversationMetadata] =
|
||||
request.tweetsWithConversationMetadata
|
||||
val ancestorsRequest =
|
||||
tcs.GetAncestorsRequest(inputTweetsWithConversationMetadata.map(_.tweetId))
|
||||
|
||||
// build the tweets with conversation metadata by calling the conversation service with reduced
|
||||
// ancestors to limit to maxModuleSize
|
||||
val tweetsWithConversationMetadataFromAncestors: Stitch[Seq[TweetWithConversationMetadata]] =
|
||||
Stitch
|
||||
.callFuture(conversationServiceClient.getAncestors(ancestorsRequest))
|
||||
.map { getAncestorsResponse: tcs.GetAncestorsResponse =>
|
||||
inputTweetsWithConversationMetadata
|
||||
.zip(getAncestorsResponse.ancestors).collect {
|
||||
case (focalTweet, tcs.TweetAncestorsResult.TweetAncestors(ancestorsResult))
|
||||
if ancestorsResult.nonEmpty =>
|
||||
getTweetsInThread(focalTweet, ancestorsResult.head)
|
||||
}.flatten
|
||||
}
|
||||
|
||||
// dedupe the tweets in the list and transform the calling error to
|
||||
// return the requested tweets with conversation metadata
|
||||
val transformedTweetsWithConversationMetadata: Stitch[Seq[TweetWithConversationMetadata]] =
|
||||
tweetsWithConversationMetadataFromAncestors.transform {
|
||||
case Return(ancestors) =>
|
||||
Stitch.value(dedupeCandidates(inputTweetsWithConversationMetadata, ancestors))
|
||||
case Throw(_) =>
|
||||
Stitch.value(inputTweetsWithConversationMetadata)
|
||||
}
|
||||
|
||||
// return the candidates with empty source features from transformed tweetsWithConversationMetadata
|
||||
transformedTweetsWithConversationMetadata.map {
|
||||
responseTweetsWithConversationMetadata: Seq[TweetWithConversationMetadata] =>
|
||||
CandidatesWithSourceFeatures(
|
||||
responseTweetsWithConversationMetadata,
|
||||
FeatureMap.empty
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def getTweetsInThread(
|
||||
focalTweet: TweetWithConversationMetadata,
|
||||
ancestors: ta.TweetAncestors
|
||||
): Seq[TweetWithConversationMetadata] = {
|
||||
// Re-add the focal tweet so we can easily build modules and dedupe later.
|
||||
// Note, TweetConvoSVC returns the bottom of the thread first, so we
|
||||
// reverse them for easy rendering.
|
||||
val focalTweetWithConversationMetadata = TweetWithConversationMetadata(
|
||||
tweetId = focalTweet.tweetId,
|
||||
userId = focalTweet.userId,
|
||||
sourceTweetId = focalTweet.sourceTweetId,
|
||||
sourceUserId = focalTweet.sourceUserId,
|
||||
inReplyToTweetId = focalTweet.inReplyToTweetId,
|
||||
conversationId = Some(focalTweet.tweetId),
|
||||
ancestors = ancestors.ancestors
|
||||
)
|
||||
|
||||
val parentTweets = ancestors.ancestors.map { ancestor =>
|
||||
TweetWithConversationMetadata(
|
||||
tweetId = ancestor.tweetId,
|
||||
userId = Some(ancestor.userId),
|
||||
sourceTweetId = None,
|
||||
sourceUserId = None,
|
||||
inReplyToTweetId = None,
|
||||
conversationId = Some(focalTweet.tweetId),
|
||||
ancestors = Seq.empty
|
||||
)
|
||||
} ++ getTruncatedRootTweet(ancestors, focalTweet.tweetId)
|
||||
|
||||
val (intermediates, root) = parentTweets.splitAt(parentTweets.size - numberOfRootTweets)
|
||||
val truncatedIntermediates =
|
||||
intermediates.take(maxModuleSize - maxAncestorsInConversation).reverse
|
||||
root ++ truncatedIntermediates :+ focalTweetWithConversationMetadata
|
||||
}
|
||||
|
||||
/**
|
||||
* Ancestor store truncates at 256 ancestors. For very large reply threads, we try best effort
|
||||
* to append the root tweet to the ancestor list based on the conversationId and
|
||||
* conversationRootAuthorId. When rendering conversation modules, we can display the root tweet
|
||||
* instead of the 256th highest ancestor.
|
||||
*/
|
||||
private def getTruncatedRootTweet(
|
||||
ancestors: ta.TweetAncestors,
|
||||
focalTweetId: Long
|
||||
): Option[TweetWithConversationMetadata] = {
|
||||
ancestors.conversationRootAuthorId.collect {
|
||||
case rootAuthorId
|
||||
if ancestors.state == ta.ReplyState.Partial &&
|
||||
ancestors.ancestors.last.tweetId != ancestors.conversationId =>
|
||||
TweetWithConversationMetadata(
|
||||
tweetId = ancestors.conversationId,
|
||||
userId = Some(rootAuthorId),
|
||||
sourceTweetId = None,
|
||||
sourceUserId = None,
|
||||
inReplyToTweetId = None,
|
||||
conversationId = Some(focalTweetId),
|
||||
ancestors = Seq.empty
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def dedupeCandidates(
|
||||
inputTweetsWithConversationMetadata: Seq[TweetWithConversationMetadata],
|
||||
ancestors: Seq[TweetWithConversationMetadata]
|
||||
): Seq[TweetWithConversationMetadata] = {
|
||||
val dedupedAncestors: Iterable[TweetWithConversationMetadata] = ancestors
|
||||
.groupBy(_.tweetId).map {
|
||||
case (_, duplicateAncestors)
|
||||
if duplicateAncestors.size > maxTweetsInConversationWithSameId =>
|
||||
duplicateAncestors.maxBy(_.conversationId.getOrElse(0L))
|
||||
case (_, nonDuplicateAncestors) => nonDuplicateAncestors.head
|
||||
}
|
||||
// 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.
|
||||
val sortedDedupedAncestors: Seq[TweetWithConversationMetadata] =
|
||||
dedupedAncestors.toSeq.sortBy(_.tweetId)
|
||||
|
||||
val ancestorIds = sortedDedupedAncestors.map(_.tweetId).toSet
|
||||
val updatedCandidates = inputTweetsWithConversationMetadata.filterNot { candidate =>
|
||||
ancestorIds.contains(candidate.tweetId)
|
||||
}
|
||||
sortedDedupedAncestors ++ updatedCandidates
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.tweetconvosvc
|
||||
|
||||
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.transformer.CandidateFeatureTransformer
|
||||
import com.twitter.product_mixer.core.model.common.identifier.TransformerIdentifier
|
||||
import com.twitter.timelineservice.suggests.thriftscala.SuggestType
|
||||
|
||||
object AuthorIdFeature extends Feature[TweetCandidate, Option[Long]]
|
||||
object AncestorIdsFeature extends Feature[TweetCandidate, Seq[Long]]
|
||||
object ConversationModuleFocalTweetIdFeature extends Feature[TweetCandidate, Option[Long]]
|
||||
object InReplyToFeature extends Feature[TweetCandidate, Option[Long]]
|
||||
object IsRetweetFeature extends Feature[TweetCandidate, Boolean]
|
||||
object SourceTweetIdFeature extends Feature[TweetCandidate, Option[Long]]
|
||||
object SourceUserIdFeature extends Feature[TweetCandidate, Option[Long]]
|
||||
object SuggestTypeFeature extends Feature[TweetCandidate, Option[SuggestType]]
|
||||
|
||||
object ConversationServiceResponseFeatureTransformer
|
||||
extends CandidateFeatureTransformer[TweetWithConversationMetadata] {
|
||||
override val identifier: TransformerIdentifier =
|
||||
TransformerIdentifier("ConversationServiceResponse")
|
||||
|
||||
override val features: Set[Feature[_, _]] =
|
||||
Set(
|
||||
AuthorIdFeature,
|
||||
InReplyToFeature,
|
||||
IsRetweetFeature,
|
||||
SourceTweetIdFeature,
|
||||
SourceUserIdFeature,
|
||||
ConversationModuleFocalTweetIdFeature,
|
||||
AncestorIdsFeature,
|
||||
SuggestTypeFeature
|
||||
)
|
||||
|
||||
override def transform(candidate: TweetWithConversationMetadata): FeatureMap = {
|
||||
FeatureMapBuilder()
|
||||
.add(AuthorIdFeature, candidate.userId)
|
||||
.add(InReplyToFeature, candidate.inReplyToTweetId)
|
||||
.add(IsRetweetFeature, candidate.sourceTweetId.isDefined)
|
||||
.add(SourceTweetIdFeature, candidate.sourceTweetId)
|
||||
.add(SourceUserIdFeature, candidate.sourceUserId)
|
||||
.add(ConversationModuleFocalTweetIdFeature, candidate.conversationId)
|
||||
.add(AncestorIdsFeature, candidate.ancestors.map(_.tweetId))
|
||||
.add(SuggestTypeFeature, Some(SuggestType.OrganicConversation))
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.candidate_source.tweetconvosvc
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.common.CandidateScope
|
||||
import com.twitter.product_mixer.core.functional_component.selector.Selector
|
||||
import com.twitter.product_mixer.core.functional_component.selector.SelectorResult
|
||||
import com.twitter.product_mixer.core.model.common.presentation.CandidateWithDetails
|
||||
import com.twitter.product_mixer.core.model.common.presentation.ModuleCandidateWithDetails
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
/**
|
||||
* Takes a conversation module item and truncates it to be at most the focal tweet, the focal tweet's
|
||||
* in reply to tweet and optionally, the root conversation tweet if desired.
|
||||
* @param pipelineScope What pipeline scopes to include in this.
|
||||
* @param includeRootTweet Whether to include the root tweet at the top of the conversation or not.
|
||||
* @tparam Query
|
||||
*/
|
||||
case class DropMaxConversationModuleItemCandidates[-Query <: PipelineQuery](
|
||||
override val pipelineScope: CandidateScope,
|
||||
includeRootTweet: Boolean)
|
||||
extends Selector[Query] {
|
||||
override def apply(
|
||||
query: Query,
|
||||
remainingCandidates: Seq[CandidateWithDetails],
|
||||
result: Seq[CandidateWithDetails]
|
||||
): SelectorResult = {
|
||||
val updatedCandidates = remainingCandidates.collect {
|
||||
case moduleCandidate: ModuleCandidateWithDetails if pipelineScope.contains(moduleCandidate) =>
|
||||
updateConversationModule(moduleCandidate, includeRootTweet)
|
||||
case candidates => candidates
|
||||
}
|
||||
SelectorResult(remainingCandidates = updatedCandidates, result = result)
|
||||
}
|
||||
|
||||
private def updateConversationModule(
|
||||
module: ModuleCandidateWithDetails,
|
||||
includeRootTweet: Boolean
|
||||
): ModuleCandidateWithDetails = {
|
||||
// If the thread is only the root tweet & a focal tweet replying to it, no truncation can be done.
|
||||
if (module.candidates.length <= 2) {
|
||||
module
|
||||
} else {
|
||||
// If a thread is more 3 or more tweets, we optionally keep the root tweet if desired, and take
|
||||
// the focal tweet tweet and its direct ancestor (the one it would have replied to) and return
|
||||
// those.
|
||||
val tweetCandidates = module.candidates
|
||||
val replyAndFocalTweet = tweetCandidates.takeRight(2)
|
||||
val updatedConversation = if (includeRootTweet) {
|
||||
tweetCandidates.headOption ++ replyAndFocalTweet
|
||||
} else {
|
||||
replyAndFocalTweet
|
||||
}
|
||||
module.copy(candidates = updatedConversation.toSeq)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/decorator/slice/builder",
|
||||
"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/presentation/slice",
|
||||
"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/slice/builder",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/decorator/slice/builder",
|
||||
"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/presentation/slice",
|
||||
"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/slice/builder",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
],
|
||||
)
|
|
@ -1,41 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.slice
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.CursorCandidate
|
||||
import com.twitter.product_mixer.component_library.model.presentation.slice.SliceItemPresentation
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.DecoratorIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.slice.CursorItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.CandidateDecorator
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.Decoration
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.slice.builder.CandidateSliceItemBuilder
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* Adds a [[Decoration]] for all `candidates` that are [[CursorCandidate]]s
|
||||
*
|
||||
* @note Only [[CursorCandidate]]s get decorated in [[SliceItemCandidateDecorator]]
|
||||
* because the [[com.twitter.product_mixer.component_library.premarshaller.slice.SliceDomainMarshaller]]
|
||||
* handles the undecorated non-[[CursorCandidate]] `candidates` directly.
|
||||
*/
|
||||
case class SliceItemCandidateDecorator[Query <: PipelineQuery, Candidate <: UniversalNoun[Any]](
|
||||
cursorBuilder: CandidateSliceItemBuilder[Query, CursorCandidate, CursorItem],
|
||||
override val identifier: DecoratorIdentifier = DecoratorIdentifier("SliceItemCandidate"))
|
||||
extends CandidateDecorator[Query, Candidate] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[Seq[Decoration]] = {
|
||||
val cursorPresentations = candidates.collect {
|
||||
case CandidateWithFeatures(candidate: CursorCandidate, features) =>
|
||||
val cursorItem = cursorBuilder(query, candidate, features)
|
||||
val presentation = SliceItemPresentation(sliceItem = cursorItem)
|
||||
|
||||
Decoration(candidate, presentation)
|
||||
}
|
||||
|
||||
Stitch.value(cursorPresentations)
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"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/decorator/slice/builder",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
],
|
||||
exports = [
|
||||
"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/decorator/slice/builder",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
],
|
||||
)
|
|
@ -1,29 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.slice.builder
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.CursorCandidate
|
||||
import com.twitter.product_mixer.component_library.model.candidate.{
|
||||
NextCursor => CursorCandidateNextCursor
|
||||
}
|
||||
import com.twitter.product_mixer.component_library.model.candidate.{
|
||||
PreviousCursor => CursorCandidatePreviousCursor
|
||||
}
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.slice.CursorItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.slice.NextCursor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.slice.PreviousCursor
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.slice.builder.CandidateSliceItemBuilder
|
||||
|
||||
case class CursorCandidateSliceItemBuilder()
|
||||
extends CandidateSliceItemBuilder[PipelineQuery, CursorCandidate, CursorItem] {
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidate: CursorCandidate,
|
||||
featureMap: FeatureMap
|
||||
): CursorItem =
|
||||
candidate.cursorType match {
|
||||
case CursorCandidateNextCursor => CursorItem(candidate.value, NextCursor)
|
||||
case CursorCandidatePreviousCursor => CursorItem(candidate.value, PreviousCursor)
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
scala_library(
|
||||
name = "urt",
|
||||
sources = ["**/*.scala"] + exclude_globs(["builder/richtext/*.scala"]),
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
scalac_plugins = ["no-roomba"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
":richtext",
|
||||
"3rdparty/jvm/com/twitter/bijection:json",
|
||||
"3rdparty/jvm/com/twitter/bijection:scrooge",
|
||||
"explore/explore-mixer/server/src/main/scala/com/twitter/explore_mixer/model/request",
|
||||
"interests-mixer/server/src/main/scala/com/twitter/interests_mixer/model/request",
|
||||
"onboarding/service/thrift/src/main/thrift:thrift-scala",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/candidate_source/people_discovery",
|
||||
"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/candidate/suggestion",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate/trends_events",
|
||||
"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/flexible_injection_pipeline/transformer",
|
||||
"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/urt/builder",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common/presentation/urt",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/alert",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/cover",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/icon",
|
||||
"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/model/marshalling/response/urt/metadata",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/operation",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/timeline_module",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"src/thrift/com/twitter/ads/adserver:ad_metadata_container-scala",
|
||||
"src/thrift/com/twitter/ads/adserver:adserver_common-scala",
|
||||
"src/thrift/com/twitter/hermit:hermit-scala",
|
||||
"src/thrift/com/twitter/suggests/controller_data:controller_data-scala",
|
||||
"src/thrift/com/twitter/timelines/service:thrift-scala",
|
||||
"src/thrift/com/twitter/timelinescorer/common/scoredtweetcandidate:thrift-scala",
|
||||
"src/thrift/com/twitter/timelineservice/server/internal:thrift-scala",
|
||||
"stringcenter/client",
|
||||
"stringcenter/client/src/main/java",
|
||||
"stringcenter/client/src/main/scala/com/twitter/stringcenter/client",
|
||||
"timelines/src/main/scala/com/twitter/timelines/util",
|
||||
"trends/trending_content/src/main/scala/com/twitter/trends/trending_content/util:compacting-number-localizer",
|
||||
"tweetconvosvc/common/src/main/thrift/com/twitter/tweetconvosvc/tweet_ancestor:thrift-scala",
|
||||
"twitter-text/lib/java/src/main/java/com/twitter/twittertext",
|
||||
],
|
||||
exports = [
|
||||
":richtext",
|
||||
"3rdparty/jvm/com/twitter/bijection:json",
|
||||
"3rdparty/jvm/com/twitter/bijection:scrooge",
|
||||
"explore/explore-mixer/server/src/main/scala/com/twitter/explore_mixer/model/request",
|
||||
"interests-mixer/server/src/main/scala/com/twitter/interests_mixer/model/request",
|
||||
"onboarding/service/thrift/src/main/thrift:thrift-scala",
|
||||
"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/candidate/suggestion",
|
||||
"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/flexible_injection_pipeline/transformer",
|
||||
"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/urt/builder",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common/presentation/urt",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/alert",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/cover",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/icon",
|
||||
"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/model/marshalling/response/urt/metadata",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/operation",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/timeline_module",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"src/thrift/com/twitter/ads/adserver:ad_metadata_container-scala",
|
||||
"src/thrift/com/twitter/ads/adserver:adserver_common-scala",
|
||||
"src/thrift/com/twitter/suggests/controller_data:controller_data-scala",
|
||||
"src/thrift/com/twitter/timelines/service:thrift-scala",
|
||||
"src/thrift/com/twitter/timelinescorer/common/scoredtweetcandidate:thrift-scala",
|
||||
"src/thrift/com/twitter/timelineservice/server/internal:thrift-scala",
|
||||
"stringcenter/client",
|
||||
"stringcenter/client/src/main/java",
|
||||
"stringcenter/client/src/main/scala/com/twitter/stringcenter/client",
|
||||
"timelines/src/main/scala/com/twitter/timelines/util",
|
||||
"tweetconvosvc/common/src/main/thrift/com/twitter/tweetconvosvc/tweet_ancestor:thrift-scala",
|
||||
"twitter-text/lib/java/src/main/java/com/twitter/twittertext",
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "richtext",
|
||||
sources = ["builder/richtext/*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature/featuremap",
|
||||
"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/model/marshalling/response/urt/metadata",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/richtext",
|
||||
"twitter-text/lib/java/src/main/java/com/twitter/twittertext",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature/featuremap",
|
||||
"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/model/marshalling/response/urt/metadata",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/marshalling/response/urt/richtext",
|
||||
"twitter-text/lib/java/src/main/java/com/twitter/twittertext",
|
||||
],
|
||||
)
|
|
@ -1,44 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.tweet.TweetCandidateUrtItemBuilder
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
||||
import com.twitter.product_mixer.component_library.model.presentation.urt.ConversationModuleItem
|
||||
import com.twitter.product_mixer.component_library.model.presentation.urt.UrtItemPresentation
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.CandidateDecorator
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.Decoration
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.DecoratorIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.ModuleItemTreeDisplay
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
case class UrtConversationItemCandidateDecorator[
|
||||
Query <: PipelineQuery,
|
||||
Candidate <: BaseTweetCandidate
|
||||
](
|
||||
tweetCandidateUrtItemBuilder: TweetCandidateUrtItemBuilder[Query, Candidate],
|
||||
override val identifier: DecoratorIdentifier = DecoratorIdentifier("UrtConversationItem"))
|
||||
extends CandidateDecorator[Query, Candidate] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[Seq[Decoration]] = {
|
||||
val candidatePresentations = candidates.view.zipWithIndex.map {
|
||||
case (candidate, index) =>
|
||||
val itemPresentation = new UrtItemPresentation(
|
||||
timelineItem = tweetCandidateUrtItemBuilder(
|
||||
pipelineQuery = query,
|
||||
tweetCandidate = candidate.candidate,
|
||||
candidateFeatures = candidate.features)
|
||||
) with ConversationModuleItem {
|
||||
override val treeDisplay: Option[ModuleItemTreeDisplay] = None
|
||||
override val dispensable: Boolean = index < candidates.length - 1
|
||||
}
|
||||
|
||||
Decoration(candidate.candidate, itemPresentation)
|
||||
}
|
||||
|
||||
Stitch.value(candidatePresentations)
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.presentation.urt.UrtItemPresentation
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.DecoratorIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.CandidateDecorator
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.Decoration
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* Decorator that will apply the provided [[CandidateUrtEntryBuilder]] to each candidate independently to make a [[TimelineItem]]
|
||||
*/
|
||||
case class UrtItemCandidateDecorator[
|
||||
Query <: PipelineQuery,
|
||||
BuilderInput <: UniversalNoun[Any],
|
||||
BuilderOutput <: TimelineItem
|
||||
](
|
||||
builder: CandidateUrtEntryBuilder[Query, BuilderInput, BuilderOutput],
|
||||
override val identifier: DecoratorIdentifier = DecoratorIdentifier("UrtItemCandidate"))
|
||||
extends CandidateDecorator[Query, BuilderInput] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[BuilderInput]]
|
||||
): Stitch[Seq[Decoration]] = {
|
||||
val candidatePresentations = candidates.map { candidate =>
|
||||
val itemPresentation = UrtItemPresentation(
|
||||
timelineItem = builder(query, candidate.candidate, candidate.features)
|
||||
)
|
||||
|
||||
Decoration(candidate.candidate, itemPresentation)
|
||||
}
|
||||
|
||||
Stitch.value(candidatePresentations)
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.presentation.urt.UrtItemPresentation
|
||||
import com.twitter.product_mixer.component_library.model.presentation.urt.UrtModulePresentation
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.DecoratorIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.CandidateDecorator
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.Decoration
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.timeline_module.BaseTimelineModuleBuilder
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* Decorator that will apply the provided [[urtItemCandidateDecorator]] to all the `candidates` and apply
|
||||
* the same [[UrtModulePresentation]] from [[moduleBuilder]] to each Candidate.
|
||||
*/
|
||||
case class UrtItemInModuleDecorator[
|
||||
Query <: PipelineQuery,
|
||||
BuilderInput <: UniversalNoun[Any],
|
||||
BuilderOutput <: TimelineItem
|
||||
](
|
||||
urtItemCandidateDecorator: CandidateDecorator[Query, BuilderInput],
|
||||
moduleBuilder: BaseTimelineModuleBuilder[Query, BuilderInput],
|
||||
override val identifier: DecoratorIdentifier = DecoratorIdentifier("UrtItemInModule"))
|
||||
extends CandidateDecorator[Query, BuilderInput] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[BuilderInput]]
|
||||
): Stitch[Seq[Decoration]] = {
|
||||
if (candidates.nonEmpty) {
|
||||
val urtItemCandidatesWithDecoration = urtItemCandidateDecorator(query, candidates)
|
||||
|
||||
// Pass candidates to support when the module is constructed dynamically based on the list
|
||||
val modulePresentation =
|
||||
UrtModulePresentation(moduleBuilder(query, candidates))
|
||||
|
||||
urtItemCandidatesWithDecoration.map { candidates =>
|
||||
candidates.collect {
|
||||
case Decoration(candidate, urtItemPresentation: UrtItemPresentation) =>
|
||||
Decoration(
|
||||
candidate,
|
||||
urtItemPresentation.copy(modulePresentation = Some(modulePresentation)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Stitch.Nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.presentation.urt.UrtItemPresentation
|
||||
import com.twitter.product_mixer.component_library.model.presentation.urt.UrtModulePresentation
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.DecoratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.CandidateDecorator
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.Decoration
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.ModuleIdGeneration
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.AutomaticUniqueModuleId
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.timeline_module.BaseTimelineModuleBuilder
|
||||
|
||||
/**
|
||||
* Given a [[CandidateWithFeatures]] return the corresponding group with which it should be
|
||||
* associated. Returning none will result in the candidate not being assigned to any module.
|
||||
*/
|
||||
trait GroupByKey[-Query <: PipelineQuery, -BuilderInput <: UniversalNoun[Any], Key] {
|
||||
def apply(query: Query, candidate: BuilderInput, candidateFeatures: FeatureMap): Option[Key]
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to [[UrtItemInModuleDecorator]] except that this decorator can assign items to different
|
||||
* modules based on the provided [[GroupByKey]].
|
||||
*
|
||||
* @param urtItemCandidateDecorator decorates individual item candidates
|
||||
* @param moduleBuilder builds a module from a particular candidate group
|
||||
* @param groupByKey assigns each candidate a module group. Returning [[None]] will result in the
|
||||
* candidate not being assigned to a module
|
||||
*/
|
||||
case class UrtMultipleModulesDecorator[
|
||||
-Query <: PipelineQuery,
|
||||
-BuilderInput <: UniversalNoun[Any],
|
||||
GroupKey
|
||||
](
|
||||
urtItemCandidateDecorator: CandidateDecorator[Query, BuilderInput],
|
||||
moduleBuilder: BaseTimelineModuleBuilder[Query, BuilderInput],
|
||||
groupByKey: GroupByKey[Query, BuilderInput, GroupKey],
|
||||
override val identifier: DecoratorIdentifier = DecoratorIdentifier("UrtMultipleModules"))
|
||||
extends CandidateDecorator[Query, BuilderInput] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[BuilderInput]]
|
||||
): Stitch[Seq[Decoration]] = {
|
||||
if (candidates.nonEmpty) {
|
||||
|
||||
/** Individual candidates with [[UrtItemPresentation]]s */
|
||||
val decoratedCandidatesStitch: Stitch[
|
||||
Seq[(CandidateWithFeatures[BuilderInput], Decoration)]
|
||||
] = urtItemCandidateDecorator(query, candidates).map(candidates.zip(_))
|
||||
|
||||
decoratedCandidatesStitch.map { decoratedCandidates =>
|
||||
// Group candidates into modules
|
||||
val candidatesByModule: Map[Option[GroupKey], Seq[
|
||||
(CandidateWithFeatures[BuilderInput], Decoration)
|
||||
]] =
|
||||
decoratedCandidates.groupBy {
|
||||
case (CandidateWithFeatures(candidate, features), _) =>
|
||||
groupByKey(query, candidate, features)
|
||||
}
|
||||
|
||||
candidatesByModule.iterator.zipWithIndex.flatMap {
|
||||
|
||||
// A None group key indicates these candidates should not be put into a module. Return
|
||||
// the decorated candidates.
|
||||
case ((None, candidateGroup), _) =>
|
||||
candidateGroup.map {
|
||||
case (_, decoration) => decoration
|
||||
}
|
||||
|
||||
// Build a UrtModulePresentation and add it to each candidate's decoration.
|
||||
case ((_, candidateGroup), index) =>
|
||||
val (candidatesWithFeatures, decorations) = candidateGroup.unzip
|
||||
|
||||
/**
|
||||
* Build the module and update its ID if [[AutomaticUniqueModuleId]]s are being used.
|
||||
* Forcing IDs to be different ensures that modules are never accidentally grouped
|
||||
* together, since all other fields might otherwise be equal (candidates aren't added
|
||||
* to modules until the domain marshalling phase).
|
||||
*/
|
||||
val timelineModule = {
|
||||
val module = moduleBuilder(query, candidatesWithFeatures)
|
||||
|
||||
ModuleIdGeneration(module.id) match {
|
||||
case id: AutomaticUniqueModuleId => module.copy(id = id.withOffset(index).moduleId)
|
||||
case _ => module
|
||||
}
|
||||
}
|
||||
|
||||
val modulePresentation = UrtModulePresentation(timelineModule)
|
||||
|
||||
decorations.collect {
|
||||
case Decoration(candidate, urtItemPresentation: UrtItemPresentation) =>
|
||||
Decoration(
|
||||
candidate,
|
||||
urtItemPresentation.copy(modulePresentation = Some(modulePresentation)))
|
||||
}
|
||||
}.toSeq
|
||||
}
|
||||
} else {
|
||||
Stitch.Nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.contextual_ref
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.contextual_ref.ContextualTweetRef
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.contextual_ref.TweetHydrationContext
|
||||
|
||||
case class ContextualTweetRefBuilder[-Candidate <: BaseTweetCandidate](
|
||||
tweetHydrationContext: TweetHydrationContext) {
|
||||
|
||||
def apply(candidate: Candidate): Option[ContextualTweetRef] =
|
||||
Some(ContextualTweetRef(candidate.id, Some(tweetHydrationContext)))
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.conversations
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.timeline_module.BaseModuleMetadataBuilder
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.timeline_module.ModuleConversationMetadata
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.timeline_module.ModuleMetadata
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
case class ConversationModuleMetadataBuilder[
|
||||
Query <: PipelineQuery,
|
||||
Candidate <: BaseTweetCandidate
|
||||
](
|
||||
ancestorIdsFeature: Feature[_, Seq[Long]],
|
||||
allIdsOrdering: Ordering[Long])
|
||||
extends BaseModuleMetadataBuilder[Query, Candidate] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): ModuleMetadata = {
|
||||
|
||||
val ancestors = candidates.last.features.getOrElse(ancestorIdsFeature, Seq.empty)
|
||||
val sortedAllTweetIds = (candidates.last.candidate.id +: ancestors).sorted(allIdsOrdering)
|
||||
|
||||
ModuleMetadata(
|
||||
adsMetadata = None,
|
||||
conversationMetadata = Some(
|
||||
ModuleConversationMetadata(
|
||||
allTweetIds = Some(sortedAllTweetIds),
|
||||
socialContext = None,
|
||||
enableDeduplication = Some(true)
|
||||
)),
|
||||
gridCarouselMetadata = None
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,205 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.flexible_injection_pipeline
|
||||
|
||||
import com.twitter.onboarding.injections.thriftscala.Injection
|
||||
import com.twitter.onboarding.injections.{thriftscala => onboardingthrift}
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.flexible_injection_pipeline.OnboardingInjectionConversions._
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BasePromptCandidate
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.flexible_injection_pipeline.transformer.FlipPromptCarouselTileFeature
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.flexible_injection_pipeline.transformer.FlipPromptInjectionsFeature
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.flexible_injection_pipeline.transformer.FlipPromptOffsetInModuleFeature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.marshaller.TransportMarshaller
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.CoverFullCoverDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.CoverHalfCoverDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.FullCover
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.FullCoverContent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.HalfCover
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.HalfCoverContent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.HeaderImagePromptMessageContent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.InlinePromptMessageContent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.MessageContent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.MessagePromptItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.prompt.PromptItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.ClientEventDetails
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.ClientEventInfo
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.TimelinesDetails
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object FlipPromptCandidateUrtItemBuilder {
|
||||
val FlipPromptClientEventInfoElement: String = "flip-prompt-message"
|
||||
}
|
||||
|
||||
case class FlipPromptCandidateUrtItemBuilder[-Query <: PipelineQuery]()
|
||||
extends CandidateUrtEntryBuilder[Query, BasePromptCandidate[Any], TimelineItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
promptCandidate: BasePromptCandidate[Any],
|
||||
candidateFeatures: FeatureMap
|
||||
): TimelineItem = {
|
||||
val injection = candidateFeatures.get(FlipPromptInjectionsFeature)
|
||||
|
||||
injection match {
|
||||
case onboardingthrift.Injection.InlinePrompt(candidate) =>
|
||||
MessagePromptItem(
|
||||
id = promptCandidate.id.toString,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = buildClientEventInfo(injection),
|
||||
feedbackActionInfo = candidate.feedbackInfo.map(convertFeedbackInfo),
|
||||
isPinned = Some(candidate.isPinnedEntry),
|
||||
content = getInlinePromptMessageContent(candidate),
|
||||
impressionCallbacks = candidate.impressionCallbacks.map(_.map(convertCallback).toList)
|
||||
)
|
||||
case onboardingthrift.Injection.FullCover(candidate) =>
|
||||
FullCover(
|
||||
id = promptCandidate.id.toString,
|
||||
// Note that sort index is not used for Covers, as they are not TimelineEntry and do not have entryId
|
||||
sortIndex = None,
|
||||
clientEventInfo =
|
||||
Some(OnboardingInjectionConversions.convertClientEventInfo(candidate.clientEventInfo)),
|
||||
content = getFullCoverContent(candidate)
|
||||
)
|
||||
case onboardingthrift.Injection.HalfCover(candidate) =>
|
||||
HalfCover(
|
||||
id = promptCandidate.id.toString,
|
||||
// Note that sort index is not used for Covers, as they are not TimelineEntry and do not have entryId
|
||||
sortIndex = None,
|
||||
clientEventInfo =
|
||||
Some(OnboardingInjectionConversions.convertClientEventInfo(candidate.clientEventInfo)),
|
||||
content = getHalfCoverContent(candidate)
|
||||
)
|
||||
case Injection.TilesCarousel(_) =>
|
||||
val offsetInModuleOption =
|
||||
candidateFeatures.get(FlipPromptOffsetInModuleFeature)
|
||||
val offsetInModule =
|
||||
offsetInModuleOption.getOrElse(throw FlipPromptOffsetInModuleMissing)
|
||||
val tileOption =
|
||||
candidateFeatures.get(FlipPromptCarouselTileFeature)
|
||||
val tile = tileOption.getOrElse(throw FlipPromptCarouselTileMissing)
|
||||
TilesCarouselConversions.convertTile(tile, offsetInModule)
|
||||
case onboardingthrift.Injection.RelevancePrompt(candidate) =>
|
||||
PromptItem(
|
||||
id = promptCandidate.id.toString,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = buildClientEventInfo(injection),
|
||||
content = RelevancePromptConversions.convertContent(candidate),
|
||||
impressionCallbacks = Some(candidate.impressionCallbacks.map(convertCallback).toList)
|
||||
)
|
||||
case _ => throw new UnsupportedFlipPromptException(injection)
|
||||
}
|
||||
}
|
||||
|
||||
private def getInlinePromptMessageContent(
|
||||
candidate: onboardingthrift.InlinePrompt
|
||||
): MessageContent = {
|
||||
candidate.image match {
|
||||
case Some(image) =>
|
||||
HeaderImagePromptMessageContent(
|
||||
headerImage = convertImage(image),
|
||||
headerText = Some(candidate.headerText.text),
|
||||
bodyText = candidate.bodyText.map(_.text),
|
||||
primaryButtonAction = candidate.primaryAction.map(convertButtonAction),
|
||||
secondaryButtonAction = candidate.secondaryAction.map(convertButtonAction),
|
||||
headerRichText = Some(convertRichText(candidate.headerText)),
|
||||
bodyRichText = candidate.bodyText.map(convertRichText),
|
||||
action =
|
||||
None
|
||||
)
|
||||
case None =>
|
||||
InlinePromptMessageContent(
|
||||
headerText = candidate.headerText.text,
|
||||
bodyText = candidate.bodyText.map(_.text),
|
||||
primaryButtonAction = candidate.primaryAction.map(convertButtonAction),
|
||||
secondaryButtonAction = candidate.secondaryAction.map(convertButtonAction),
|
||||
headerRichText = Some(convertRichText(candidate.headerText)),
|
||||
bodyRichText = candidate.bodyText.map(convertRichText),
|
||||
socialContext = candidate.socialContext.map(convertSocialContext),
|
||||
userFacepile = candidate.promptUserFacepile.map(convertUserFacePile)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def getFullCoverContent(
|
||||
candidate: onboardingthrift.FullCover
|
||||
): FullCoverContent =
|
||||
FullCoverContent(
|
||||
displayType = CoverFullCoverDisplayType,
|
||||
primaryText = convertRichText(candidate.primaryText),
|
||||
primaryCoverCta = convertCoverCta(candidate.primaryButtonAction),
|
||||
secondaryCoverCta = candidate.secondaryButtonAction.map(convertCoverCta),
|
||||
secondaryText = candidate.secondaryText.map(convertRichText),
|
||||
imageVariant = candidate.image.map(img => convertImageVariant(img.image)),
|
||||
details = candidate.detailText.map(convertRichText),
|
||||
dismissInfo = candidate.dismissInfo.map(convertDismissInfo),
|
||||
imageDisplayType = candidate.image.map(img => convertImageDisplayType(img.imageDisplayType)),
|
||||
impressionCallbacks = candidate.impressionCallbacks.map(_.map(convertCallback).toList)
|
||||
)
|
||||
|
||||
private def getHalfCoverContent(
|
||||
candidate: onboardingthrift.HalfCover
|
||||
): HalfCoverContent =
|
||||
HalfCoverContent(
|
||||
displayType =
|
||||
candidate.displayType.map(convertHalfCoverDisplayType).getOrElse(CoverHalfCoverDisplayType),
|
||||
primaryText = convertRichText(candidate.primaryText),
|
||||
primaryCoverCta = convertCoverCta(candidate.primaryButtonAction),
|
||||
secondaryCoverCta = candidate.secondaryButtonAction.map(convertCoverCta),
|
||||
secondaryText = candidate.secondaryText.map(convertRichText),
|
||||
coverImage = candidate.image.map(convertCoverImage),
|
||||
dismissible = candidate.dismissible,
|
||||
dismissInfo = candidate.dismissInfo.map(convertDismissInfo),
|
||||
impressionCallbacks = candidate.impressionCallbacks.map(_.map(convertCallback).toList)
|
||||
)
|
||||
|
||||
private def buildClientEventInfo(
|
||||
injection: Injection
|
||||
): Option[ClientEventInfo] = {
|
||||
injection match {
|
||||
//To keep parity between TimelineMixer and Product Mixer, inline prompt switches sets the prompt product identifier as the component and no element. Also includes clientEventDetails
|
||||
case onboardingthrift.Injection.InlinePrompt(candidate) =>
|
||||
val clientEventDetails: ClientEventDetails =
|
||||
ClientEventDetails(
|
||||
conversationDetails = None,
|
||||
timelinesDetails = Some(TimelinesDetails(injectionType = Some("Message"), None, None)),
|
||||
articleDetails = None,
|
||||
liveEventDetails = None,
|
||||
commerceDetails = None
|
||||
)
|
||||
Some(
|
||||
ClientEventInfo(
|
||||
component = candidate.injectionIdentifier,
|
||||
element = None,
|
||||
details = Some(clientEventDetails),
|
||||
action = None,
|
||||
entityToken = None))
|
||||
// To keep parity between TLM and PM we swap component and elements.
|
||||
case onboardingthrift.Injection.RelevancePrompt(candidate) =>
|
||||
Some(
|
||||
ClientEventInfo(
|
||||
// Identifier is prefixed with onboarding per TLM
|
||||
component = Some("onboarding_" + candidate.injectionIdentifier),
|
||||
element = Some("relevance_prompt"),
|
||||
details = None,
|
||||
action = None,
|
||||
entityToken = None
|
||||
))
|
||||
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UnsupportedFlipPromptException(injection: onboardingthrift.Injection)
|
||||
extends UnsupportedOperationException(
|
||||
"Unsupported timeline item " + TransportMarshaller.getSimpleName(injection.getClass))
|
||||
|
||||
object FlipPromptOffsetInModuleMissing
|
||||
extends NoSuchElementException(
|
||||
"FlipPromptOffsetInModuleFeature must be set for the TilesCarousel FLIP injection in PromptCandidateSource")
|
||||
|
||||
object FlipPromptCarouselTileMissing
|
||||
extends NoSuchElementException(
|
||||
"FlipPromptCarouselTileFeature must be set for the TilesCarousel FLIP injection in PromptCandidateSource")
|
|
@ -1,23 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.flexible_injection_pipeline
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.GroupByKey
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.flexible_injection_pipeline.transformer.FlipPromptInjectionsFeature
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.flexible_injection_pipeline.transformer.FlipPromptOffsetInModuleFeature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object FlipPromptModuleGrouping extends GroupByKey[PipelineQuery, UniversalNoun[Any], Int] {
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidate: UniversalNoun[Any],
|
||||
candidateFeatures: FeatureMap
|
||||
): Option[Int] = {
|
||||
val injection = candidateFeatures.get(FlipPromptInjectionsFeature)
|
||||
val offsetInModule = candidateFeatures.getOrElse(FlipPromptOffsetInModuleFeature, None)
|
||||
|
||||
// We return None for any candidate that doesn't have an offsetInModule, so that they are left as independent items.
|
||||
// Otherwise, we return a hash of the injection instance which will be used to aggregate candidates with matching values into a module.
|
||||
offsetInModule.map(_ => injection.hashCode())
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.flexible_injection_pipeline
|
||||
|
||||
import com.twitter.onboarding.injections.thriftscala.Injection
|
||||
import com.twitter.onboarding.injections.{thriftscala => onboardingthrift}
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.AutomaticUniqueModuleId
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.timeline_module.ModuleIdGeneration
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BasePromptCandidate
|
||||
import com.twitter.product_mixer.component_library.pipeline.candidate.flexible_injection_pipeline.transformer.FlipPromptInjectionsFeature
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.timeline_module.BaseTimelineModuleBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.marshaller.TransportMarshaller
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.EntryNamespace
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineModule
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.timeline_module.Carousel
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
case class FlipPromptUrtModuleBuilder[-Query <: PipelineQuery](
|
||||
moduleIdGeneration: ModuleIdGeneration = AutomaticUniqueModuleId())
|
||||
extends BaseTimelineModuleBuilder[Query, BasePromptCandidate[Any]] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[BasePromptCandidate[Any]]]
|
||||
): TimelineModule = {
|
||||
val firstCandidate = candidates.head
|
||||
val injection = firstCandidate.features.get(FlipPromptInjectionsFeature)
|
||||
injection match {
|
||||
case Injection.TilesCarousel(candidate) =>
|
||||
TimelineModule(
|
||||
id = moduleIdGeneration.moduleId,
|
||||
sortIndex = None,
|
||||
entryNamespace = EntryNamespace("flip-timeline-module"),
|
||||
clientEventInfo =
|
||||
Some(OnboardingInjectionConversions.convertClientEventInfo(candidate.clientEventInfo)),
|
||||
feedbackActionInfo =
|
||||
candidate.feedbackInfo.map(OnboardingInjectionConversions.convertFeedbackInfo),
|
||||
isPinned = Some(candidate.isPinnedEntry),
|
||||
// Items are automatically set in the domain marshaller phase
|
||||
items = Seq.empty,
|
||||
displayType = Carousel,
|
||||
header = candidate.header.map(TilesCarouselConversions.convertModuleHeader),
|
||||
footer = None,
|
||||
metadata = None,
|
||||
showMoreBehavior = None
|
||||
)
|
||||
case _ => throw new UnsupportedFlipPromptInModuleException(injection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UnsupportedFlipPromptInModuleException(injection: onboardingthrift.Injection)
|
||||
extends UnsupportedOperationException(
|
||||
"Unsupported timeline item in a Flip prompt module " + TransportMarshaller.getSimpleName(
|
||||
injection.getClass))
|
|
@ -1,361 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.flexible_injection_pipeline
|
||||
|
||||
import com.twitter.onboarding.injections.{thriftscala => onboardingthrift}
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.CenterCoverHalfCoverDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.CoverBehaviorDismiss
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.CoverBehaviorNavigate
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.CoverCta
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.CoverCtaBehavior
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.CoverHalfCoverDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.CoverImage
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.cover.HalfCoverDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.icon._
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.FollowAllMessageActionType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.LargeUserFacepileDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.MessageAction
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.MessageActionType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.MessageImage
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.MessageTextAction
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.UserFacepile
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.Bounce
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.button.ButtonStyle
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.button.Default
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.button.Primary
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.button.Secondary
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.button.Text
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.button.Destructive
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.button.Neutral
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.button.DestructiveSecondary
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.button.DestructiveText
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.Callback
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.ClientEventInfo
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.DeepLink
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.Dismiss
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.DismissInfo
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.ExternalUrl
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.FeedbackAction
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.FeedbackActionInfo
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.FollowGeneralContextType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.GeneralContext
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.ImageAnimationType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.ImageDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.ImageVariant
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.FullWidth
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.Icon
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.IconSmall
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.SocialContext
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.Url
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.UrtEndpoint
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.UrtEndpointOptions
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.Center
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.Natural
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.Plain
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.ReferenceObject
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.RichText
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.RichTextAlignment
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.RichTextEntity
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.RichTextFormat
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.RichTextCashtag
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.RichTextHashtag
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.RichTextList
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.RichTextMention
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.RichTextUser
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.Strong
|
||||
|
||||
/***
|
||||
* Helper class to convert onboarding thrift to product-mixer models
|
||||
*/
|
||||
object OnboardingInjectionConversions {
|
||||
|
||||
def convertFeedbackInfo(
|
||||
feedbackInfo: onboardingthrift.FeedbackInfo
|
||||
): FeedbackActionInfo = {
|
||||
val actions = feedbackInfo.actions.map {
|
||||
case onboardingthrift.FeedbackAction.DismissAction(dismissAction) =>
|
||||
FeedbackAction(
|
||||
Dismiss,
|
||||
prompt = dismissAction.prompt,
|
||||
confirmation = dismissAction.confirmation,
|
||||
hasUndoAction = dismissAction.hasUndoAction,
|
||||
feedbackUrl = dismissAction.feedbackUrl,
|
||||
childFeedbackActions =
|
||||
None,
|
||||
confirmationDisplayType = None,
|
||||
clientEventInfo = None,
|
||||
icon = None,
|
||||
richBehavior = None,
|
||||
subprompt = None,
|
||||
encodedFeedbackRequest = None
|
||||
)
|
||||
case onboardingthrift.FeedbackAction.UnknownUnionField(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
}
|
||||
|
||||
FeedbackActionInfo(
|
||||
feedbackActions = actions,
|
||||
feedbackMetadata = None,
|
||||
displayContext = None,
|
||||
clientEventInfo = None)
|
||||
}
|
||||
|
||||
def convertClientEventInfo(input: onboardingthrift.ClientEventInfo): ClientEventInfo =
|
||||
ClientEventInfo(
|
||||
component = input.component,
|
||||
element = input.element,
|
||||
details = None,
|
||||
action = input.action,
|
||||
entityToken = None)
|
||||
|
||||
def convertCallback(callback: onboardingthrift.Callback): Callback =
|
||||
Callback(callback.endpoint)
|
||||
|
||||
def convertImage(image: onboardingthrift.Image): MessageImage =
|
||||
MessageImage(
|
||||
Set(convertImageVariant(image.image)),
|
||||
backgroundColor =
|
||||
None
|
||||
)
|
||||
|
||||
def convertCoverImage(image: onboardingthrift.Image): CoverImage =
|
||||
CoverImage(
|
||||
convertImageVariant(image.image),
|
||||
imageDisplayType = convertImageDisplayType(image.imageDisplayType),
|
||||
imageAnimationType = image.imageAnimationType.map(convertImageAnimationType),
|
||||
)
|
||||
|
||||
def convertImageDisplayType(
|
||||
imageDisplayType: onboardingthrift.ImageDisplayType
|
||||
): ImageDisplayType =
|
||||
imageDisplayType match {
|
||||
case onboardingthrift.ImageDisplayType.Icon => Icon
|
||||
case onboardingthrift.ImageDisplayType.FullWidth => FullWidth
|
||||
case onboardingthrift.ImageDisplayType.IconSmall => IconSmall
|
||||
case onboardingthrift.ImageDisplayType.EnumUnknownImageDisplayType(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
}
|
||||
|
||||
private def convertImageAnimationType(
|
||||
imageAnimationType: onboardingthrift.ImageAnimationType
|
||||
): ImageAnimationType =
|
||||
imageAnimationType match {
|
||||
case onboardingthrift.ImageAnimationType.Bounce => Bounce
|
||||
case onboardingthrift.ImageAnimationType.EnumUnknownImageAnimationType(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
}
|
||||
|
||||
def convertImageVariant(imageVariant: onboardingthrift.ImageVariant): ImageVariant =
|
||||
ImageVariant(
|
||||
url = imageVariant.url,
|
||||
width = imageVariant.width,
|
||||
height = imageVariant.height,
|
||||
palette = None)
|
||||
|
||||
def convertButtonAction(
|
||||
buttonAction: onboardingthrift.ButtonAction
|
||||
): MessageTextAction =
|
||||
MessageTextAction(
|
||||
buttonAction.text,
|
||||
MessageAction(
|
||||
dismissOnClick = buttonAction.dismissOnClick.getOrElse(true),
|
||||
url = getActionUrl(buttonAction),
|
||||
clientEventInfo = Some(convertClientEventInfo(buttonAction.clientEventInfo)),
|
||||
onClickCallbacks = buttonAction.callbacks.map(_.map(convertCallback).toList)
|
||||
)
|
||||
)
|
||||
|
||||
private def getActionUrl(buttonAction: onboardingthrift.ButtonAction) =
|
||||
buttonAction.buttonBehavior match {
|
||||
case onboardingthrift.ButtonBehavior.Navigate(navigate) => Some(navigate.url.url)
|
||||
case onboardingthrift.ButtonBehavior.Dismiss(_) => None
|
||||
case onboardingthrift.ButtonBehavior.UnknownUnionField(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
}
|
||||
|
||||
def convertRichText(
|
||||
richText: com.twitter.onboarding.injections.thriftscala.RichText
|
||||
): RichText = {
|
||||
val entities = richText.entities.map(entity =>
|
||||
RichTextEntity(
|
||||
entity.fromIndex,
|
||||
entity.toIndex,
|
||||
entity.ref.map(convertRef),
|
||||
entity.format.map(convertFormat)))
|
||||
RichText(
|
||||
text = richText.text,
|
||||
entities = entities.toList,
|
||||
rtl = richText.rtl,
|
||||
alignment = richText.alignment.map(convertAlignment))
|
||||
}
|
||||
|
||||
private def convertAlignment(alignment: onboardingthrift.RichTextAlignment): RichTextAlignment =
|
||||
alignment match {
|
||||
case onboardingthrift.RichTextAlignment.Natural => Natural
|
||||
case onboardingthrift.RichTextAlignment.Center => Center
|
||||
case onboardingthrift.RichTextAlignment.EnumUnknownRichTextAlignment(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
}
|
||||
|
||||
private def convertRef(ref: onboardingthrift.ReferenceObject): ReferenceObject =
|
||||
ref match {
|
||||
case onboardingthrift.ReferenceObject.User(user) => RichTextUser(user.id)
|
||||
case onboardingthrift.ReferenceObject.Mention(mention) =>
|
||||
RichTextMention(mention.id, mention.screenName)
|
||||
case onboardingthrift.ReferenceObject.Hashtag(hashtag) => RichTextHashtag(hashtag.text)
|
||||
|
||||
case onboardingthrift.ReferenceObject.Cashtag(cashtag) => RichTextCashtag(cashtag.text)
|
||||
case onboardingthrift.ReferenceObject.TwitterList(twList) =>
|
||||
RichTextList(twList.id, twList.url)
|
||||
case onboardingthrift.ReferenceObject.Url(url) => RichTextHashtag(url.url)
|
||||
case onboardingthrift.ReferenceObject.UnknownUnionField(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
}
|
||||
|
||||
private def convertFormat(format: onboardingthrift.RichTextFormat): RichTextFormat =
|
||||
format match {
|
||||
case onboardingthrift.RichTextFormat.Plain => Plain
|
||||
case onboardingthrift.RichTextFormat.Strong => Strong
|
||||
case onboardingthrift.RichTextFormat.EnumUnknownRichTextFormat(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
}
|
||||
|
||||
// Specific to Message prompt
|
||||
def convertSocialContext(socialContext: onboardingthrift.RichText): SocialContext =
|
||||
GeneralContext(
|
||||
contextType = FollowGeneralContextType,
|
||||
text = socialContext.text,
|
||||
url = None,
|
||||
contextImageUrls = None,
|
||||
landingUrl = None)
|
||||
|
||||
def convertUserFacePile(
|
||||
userFacepile: onboardingthrift.PromptUserFacepile
|
||||
): UserFacepile =
|
||||
UserFacepile(
|
||||
userIds = userFacepile.userIds.toList,
|
||||
featuredUserIds = userFacepile.featuredUserIds.toList,
|
||||
action = userFacepile.action.map(convertButtonAction),
|
||||
actionType = userFacepile.actionType.map(convertUserFacePileActionType),
|
||||
displaysFeaturingText = userFacepile.displaysFeaturingText,
|
||||
displayType = Some(LargeUserFacepileDisplayType)
|
||||
)
|
||||
|
||||
private def convertUserFacePileActionType(
|
||||
actionType: onboardingthrift.FacepileActionType
|
||||
): MessageActionType =
|
||||
actionType match {
|
||||
case onboardingthrift.FacepileActionType.FollowAll => FollowAllMessageActionType
|
||||
case onboardingthrift.FacepileActionType.EnumUnknownFacepileActionType(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
}
|
||||
|
||||
// Specific to Cover
|
||||
|
||||
def convertHalfCoverDisplayType(
|
||||
displayType: onboardingthrift.HalfCoverDisplayType
|
||||
): HalfCoverDisplayType =
|
||||
displayType match {
|
||||
case onboardingthrift.HalfCoverDisplayType.Cover => CoverHalfCoverDisplayType
|
||||
case onboardingthrift.HalfCoverDisplayType.CenterCover =>
|
||||
CenterCoverHalfCoverDisplayType
|
||||
case onboardingthrift.HalfCoverDisplayType.EnumUnknownHalfCoverDisplayType(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
}
|
||||
|
||||
def convertDismissInfo(dismissInfo: onboardingthrift.DismissInfo): DismissInfo =
|
||||
DismissInfo(dismissInfo.callbacks.map(_.map(convertCallback)))
|
||||
|
||||
def convertCoverCta(
|
||||
buttonAction: onboardingthrift.ButtonAction
|
||||
): CoverCta =
|
||||
CoverCta(
|
||||
buttonAction.text,
|
||||
ctaBehavior = convertCoverCtaBehavior(buttonAction.buttonBehavior),
|
||||
callbacks = buttonAction.callbacks.map(_.map(convertCallback).toList),
|
||||
clientEventInfo = Some(convertClientEventInfo(buttonAction.clientEventInfo)),
|
||||
icon = buttonAction.icon.map(covertHorizonIcon),
|
||||
buttonStyle = buttonAction.buttonStyle.map(covertButtonStyle)
|
||||
)
|
||||
|
||||
private def convertCoverCtaBehavior(
|
||||
behavior: onboardingthrift.ButtonBehavior
|
||||
): CoverCtaBehavior =
|
||||
behavior match {
|
||||
case onboardingthrift.ButtonBehavior.Navigate(navigate) =>
|
||||
CoverBehaviorNavigate(convertUrl(navigate.url))
|
||||
case onboardingthrift.ButtonBehavior.Dismiss(dismiss) =>
|
||||
CoverBehaviorDismiss(dismiss.feedbackMessage.map(convertRichText))
|
||||
case onboardingthrift.ButtonBehavior.UnknownUnionField(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
}
|
||||
|
||||
private def covertButtonStyle(bStyle: onboardingthrift.CtaButtonStyle): ButtonStyle =
|
||||
bStyle match {
|
||||
case onboardingthrift.CtaButtonStyle.Default => Default
|
||||
case onboardingthrift.CtaButtonStyle.Primary => Primary
|
||||
case onboardingthrift.CtaButtonStyle.Secondary => Secondary
|
||||
case onboardingthrift.CtaButtonStyle.Text => Text
|
||||
case onboardingthrift.CtaButtonStyle.Destructive => Destructive
|
||||
case onboardingthrift.CtaButtonStyle.Neutral => Neutral
|
||||
case onboardingthrift.CtaButtonStyle.DestructiveSecondary => DestructiveSecondary
|
||||
case onboardingthrift.CtaButtonStyle.DestructiveText => DestructiveText
|
||||
case onboardingthrift.CtaButtonStyle.EnumUnknownCtaButtonStyle(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
}
|
||||
|
||||
private def covertHorizonIcon(icon: onboardingthrift.HorizonIcon): HorizonIcon =
|
||||
icon match {
|
||||
case onboardingthrift.HorizonIcon.Bookmark => Bookmark
|
||||
case onboardingthrift.HorizonIcon.Moment => Moment
|
||||
case onboardingthrift.HorizonIcon.Debug => Debug
|
||||
case onboardingthrift.HorizonIcon.Error => Error
|
||||
case onboardingthrift.HorizonIcon.Follow => Follow
|
||||
case onboardingthrift.HorizonIcon.Unfollow => Unfollow
|
||||
case onboardingthrift.HorizonIcon.Smile => Smile
|
||||
case onboardingthrift.HorizonIcon.Frown => Frown
|
||||
case onboardingthrift.HorizonIcon.Help => Help
|
||||
case onboardingthrift.HorizonIcon.Link => Link
|
||||
case onboardingthrift.HorizonIcon.Message => Message
|
||||
case onboardingthrift.HorizonIcon.No => No
|
||||
case onboardingthrift.HorizonIcon.Outgoing => Outgoing
|
||||
case onboardingthrift.HorizonIcon.Pin => Pin
|
||||
case onboardingthrift.HorizonIcon.Retweet => Retweet
|
||||
case onboardingthrift.HorizonIcon.Speaker => Speaker
|
||||
case onboardingthrift.HorizonIcon.Trashcan => Trashcan
|
||||
case onboardingthrift.HorizonIcon.Feedback => Feedback
|
||||
case onboardingthrift.HorizonIcon.FeedbackClose => FeedbackClose
|
||||
case onboardingthrift.HorizonIcon.EyeOff => EyeOff
|
||||
case onboardingthrift.HorizonIcon.Moderation => Moderation
|
||||
case onboardingthrift.HorizonIcon.Topic => Topic
|
||||
case onboardingthrift.HorizonIcon.TopicClose => TopicClose
|
||||
case onboardingthrift.HorizonIcon.Flag => Flag
|
||||
case onboardingthrift.HorizonIcon.TopicFilled => TopicFilled
|
||||
case onboardingthrift.HorizonIcon.NotificationsFollow => NotificationsFollow
|
||||
case onboardingthrift.HorizonIcon.Person => Person
|
||||
case onboardingthrift.HorizonIcon.Logo => Logo
|
||||
case onboardingthrift.HorizonIcon.EnumUnknownHorizonIcon(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
|
||||
}
|
||||
|
||||
def convertUrl(url: onboardingthrift.Url): Url = {
|
||||
val urlType = url.urlType match {
|
||||
case onboardingthrift.UrlType.ExternalUrl => ExternalUrl
|
||||
case onboardingthrift.UrlType.DeepLink => DeepLink
|
||||
case onboardingthrift.UrlType.UrtEndpoint => UrtEndpoint
|
||||
case onboardingthrift.UrlType.EnumUnknownUrlType(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown product: $value")
|
||||
}
|
||||
Url(urlType, url.url, url.urtEndpointOptions.map(convertUrtEndpointOptions))
|
||||
}
|
||||
|
||||
private def convertUrtEndpointOptions(
|
||||
urtEndpointOptions: onboardingthrift.UrtEndpointOptions
|
||||
): UrtEndpointOptions =
|
||||
UrtEndpointOptions(
|
||||
requestParams = urtEndpointOptions.requestParams.map(_.toMap),
|
||||
title = urtEndpointOptions.title,
|
||||
cacheId = urtEndpointOptions.cacheId,
|
||||
subtitle = urtEndpointOptions.subtitle
|
||||
)
|
||||
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.flexible_injection_pipeline
|
||||
|
||||
import com.twitter.onboarding.injections.{thriftscala => onboardingthrift}
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.prompt.Compact
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.prompt.Large
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.prompt.Normal
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.prompt.RelevancePromptContent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.prompt.RelevancePromptDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.Callback
|
||||
|
||||
/***
|
||||
* Helper class to convert Relevance Prompt related onboarding thrift to product-mixer models
|
||||
*/
|
||||
object RelevancePromptConversions {
|
||||
def convertContent(
|
||||
candidate: onboardingthrift.RelevancePrompt
|
||||
): RelevancePromptContent =
|
||||
RelevancePromptContent(
|
||||
displayType = convertDisplayType(candidate.displayType),
|
||||
title = candidate.title.text,
|
||||
confirmation = buildConfirmation(candidate),
|
||||
isRelevantText = candidate.isRelevantButton.text,
|
||||
notRelevantText = candidate.notRelevantButton.text,
|
||||
isRelevantCallback = convertCallbacks(candidate.isRelevantButton.callbacks),
|
||||
notRelevantCallback = convertCallbacks(candidate.notRelevantButton.callbacks),
|
||||
isRelevantFollowUp = None,
|
||||
notRelevantFollowUp = None
|
||||
)
|
||||
|
||||
// Based on com.twitter.timelinemixer.injection.model.candidate.OnboardingRelevancePromptDisplayType#fromThrift
|
||||
def convertDisplayType(
|
||||
displayType: onboardingthrift.RelevancePromptDisplayType
|
||||
): RelevancePromptDisplayType =
|
||||
displayType match {
|
||||
case onboardingthrift.RelevancePromptDisplayType.Normal => Normal
|
||||
case onboardingthrift.RelevancePromptDisplayType.Compact => Compact
|
||||
case onboardingthrift.RelevancePromptDisplayType.Large => Large
|
||||
case onboardingthrift.RelevancePromptDisplayType
|
||||
.EnumUnknownRelevancePromptDisplayType(value) =>
|
||||
throw new UnsupportedOperationException(s"Unknown display type: $value")
|
||||
}
|
||||
|
||||
// Based on com.twitter.timelinemixer.injection.model.injection.OnboardingRelevancePromptInjection#buildConfirmation
|
||||
def buildConfirmation(candidate: onboardingthrift.RelevancePrompt): String = {
|
||||
val isRelevantTextConfirmation =
|
||||
buttonToDismissFeedbackText(candidate.isRelevantButton).getOrElse("")
|
||||
val notRelevantTextConfirmation =
|
||||
buttonToDismissFeedbackText(candidate.notRelevantButton).getOrElse("")
|
||||
if (isRelevantTextConfirmation != notRelevantTextConfirmation)
|
||||
throw new IllegalArgumentException(
|
||||
s"""confirmation text not consistent for two buttons, :
|
||||
isRelevantConfirmation: ${isRelevantTextConfirmation}
|
||||
notRelevantConfirmation: ${notRelevantTextConfirmation}
|
||||
"""
|
||||
)
|
||||
isRelevantTextConfirmation
|
||||
}
|
||||
|
||||
// Based on com.twitter.timelinemixer.injection.model.candidate.OnboardingInjectionAction#fromThrift
|
||||
def buttonToDismissFeedbackText(button: onboardingthrift.ButtonAction): Option[String] = {
|
||||
button.buttonBehavior match {
|
||||
case onboardingthrift.ButtonBehavior.Dismiss(d) => d.feedbackMessage.map(_.text)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
// Based on com.twitter.timelinemixer.injection.model.injection.OnboardingRelevancePromptInjection#buildCallback
|
||||
def convertCallbacks(onboardingCallbacks: Option[Seq[onboardingthrift.Callback]]): Callback = {
|
||||
OnboardingInjectionConversions.convertCallback(
|
||||
onboardingCallbacks
|
||||
.flatMap(_.headOption)
|
||||
.getOrElse(
|
||||
throw new NoSuchElementException(s"Callback must be provided for the Relevance Prompt")
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.flexible_injection_pipeline
|
||||
|
||||
import com.twitter.onboarding.injections.{thriftscala => onboardingthrift}
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.flexible_injection_pipeline.OnboardingInjectionConversions.convertImageVariant
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.timeline_module.Classic
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.BlackRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.ClearRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.DeepBlueRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.DeepGrayRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.DeepGreenRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.DeepOrangeRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.DeepPurpleRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.DeepRedRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.DeepYellowRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.FadedBlueRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.FadedGrayRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.FadedGreenRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.FadedOrangeRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.FadedPurpleRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.FadedRedRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.FadedYellowRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.FaintBlueRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.FaintGrayRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.LightBlueRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.LightGrayRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.LightGreenRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.LightOrangeRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.LightPurpleRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.LightRedRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.LightYellowRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.MediumGrayRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.MediumGreenRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.MediumOrangeRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.MediumPurpleRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.MediumRedRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.MediumYellowRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.RosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.TextBlackRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.TextBlueRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.TwitterBlueRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.color.WhiteRosettaColor
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.tile.CallToActionTileContent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.tile.StandardTileContent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.tile.TileItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.Badge
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.timeline_module.ModuleHeader
|
||||
|
||||
object TilesCarouselConversions {
|
||||
// Tiles Carousel Conversions
|
||||
def convertTile(tile: onboardingthrift.Tile, id: Long): TileItem = {
|
||||
tile.content match {
|
||||
case standard: onboardingthrift.TileContent.Standard =>
|
||||
TileItem(
|
||||
id = id,
|
||||
sortIndex = None,
|
||||
clientEventInfo =
|
||||
Some(OnboardingInjectionConversions.convertClientEventInfo(tile.clientEventInfo)),
|
||||
feedbackActionInfo = None,
|
||||
title = standard.standard.title,
|
||||
supportingText = "",
|
||||
url = tile.url.map(OnboardingInjectionConversions.convertUrl),
|
||||
image = tile.image.map(img => convertImageVariant(img.image)),
|
||||
content = StandardTileContent(
|
||||
title = standard.standard.title,
|
||||
supportingText = "",
|
||||
badge = standard.standard.badge.map(convertTileBadge)
|
||||
)
|
||||
)
|
||||
case cta: onboardingthrift.TileContent.CallToAction =>
|
||||
TileItem(
|
||||
id = id,
|
||||
sortIndex = None,
|
||||
clientEventInfo =
|
||||
Some(OnboardingInjectionConversions.convertClientEventInfo(tile.clientEventInfo)),
|
||||
feedbackActionInfo = None,
|
||||
title = cta.callToAction.text,
|
||||
supportingText = "",
|
||||
url = tile.url.map(OnboardingInjectionConversions.convertUrl),
|
||||
image = None,
|
||||
content = CallToActionTileContent(
|
||||
text = cta.callToAction.text,
|
||||
richText = None,
|
||||
ctaButton = None
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
throw new UnsupportedTileCarouselConversionException(s"Tile Content: ${tile.content}")
|
||||
}
|
||||
}
|
||||
|
||||
private def convertTileBadge(badge: onboardingthrift.Badge): Badge = {
|
||||
Badge(
|
||||
text = badge.text,
|
||||
textColorName = badge.textColor.map(convertRosettaColor),
|
||||
backgroundColorName = badge.backgroundColor.map(convertRosettaColor))
|
||||
}
|
||||
|
||||
def convertModuleHeader(header: onboardingthrift.TilesCarouselHeader): ModuleHeader = {
|
||||
ModuleHeader(header.header, None, None, None, None, Classic)
|
||||
}
|
||||
|
||||
private def convertRosettaColor(color: onboardingthrift.RosettaColor): RosettaColor =
|
||||
color match {
|
||||
case onboardingthrift.RosettaColor.White => WhiteRosettaColor
|
||||
case onboardingthrift.RosettaColor.Black => BlackRosettaColor
|
||||
case onboardingthrift.RosettaColor.Clear => ClearRosettaColor
|
||||
case onboardingthrift.RosettaColor.TextBlack => TextBlackRosettaColor
|
||||
case onboardingthrift.RosettaColor.TextBlue => TextBlueRosettaColor
|
||||
|
||||
case onboardingthrift.RosettaColor.DeepGray => DeepGrayRosettaColor
|
||||
case onboardingthrift.RosettaColor.MediumGray => MediumGrayRosettaColor
|
||||
case onboardingthrift.RosettaColor.LightGray => LightGrayRosettaColor
|
||||
case onboardingthrift.RosettaColor.FadedGray => FadedGrayRosettaColor
|
||||
case onboardingthrift.RosettaColor.FaintGray => FaintGrayRosettaColor
|
||||
|
||||
case onboardingthrift.RosettaColor.DeepOrange => DeepOrangeRosettaColor
|
||||
case onboardingthrift.RosettaColor.MediumOrange => MediumOrangeRosettaColor
|
||||
case onboardingthrift.RosettaColor.LightOrange => LightOrangeRosettaColor
|
||||
case onboardingthrift.RosettaColor.FadedOrange => FadedOrangeRosettaColor
|
||||
|
||||
case onboardingthrift.RosettaColor.DeepYellow => DeepYellowRosettaColor
|
||||
case onboardingthrift.RosettaColor.MediumYellow => MediumYellowRosettaColor
|
||||
case onboardingthrift.RosettaColor.LightYellow => LightYellowRosettaColor
|
||||
case onboardingthrift.RosettaColor.FadedYellow => FadedYellowRosettaColor
|
||||
|
||||
case onboardingthrift.RosettaColor.DeepGreen => DeepGreenRosettaColor
|
||||
case onboardingthrift.RosettaColor.MediumGreen => MediumGreenRosettaColor
|
||||
case onboardingthrift.RosettaColor.LightGreen => LightGreenRosettaColor
|
||||
case onboardingthrift.RosettaColor.FadedGreen => FadedGreenRosettaColor
|
||||
|
||||
case onboardingthrift.RosettaColor.DeepBlue => DeepBlueRosettaColor
|
||||
case onboardingthrift.RosettaColor.TwitterBlue => TwitterBlueRosettaColor
|
||||
case onboardingthrift.RosettaColor.LightBlue => LightBlueRosettaColor
|
||||
case onboardingthrift.RosettaColor.FadedBlue => FadedBlueRosettaColor
|
||||
case onboardingthrift.RosettaColor.FaintBlue => FaintBlueRosettaColor
|
||||
|
||||
case onboardingthrift.RosettaColor.DeepPurple => DeepPurpleRosettaColor
|
||||
case onboardingthrift.RosettaColor.MediumPurple => MediumPurpleRosettaColor
|
||||
case onboardingthrift.RosettaColor.LightPurple => LightPurpleRosettaColor
|
||||
case onboardingthrift.RosettaColor.FadedPurple => FadedPurpleRosettaColor
|
||||
|
||||
case onboardingthrift.RosettaColor.DeepRed => DeepRedRosettaColor
|
||||
case onboardingthrift.RosettaColor.MediumRed => MediumRedRosettaColor
|
||||
case onboardingthrift.RosettaColor.LightRed => LightRedRosettaColor
|
||||
case onboardingthrift.RosettaColor.FadedRed => FadedRedRosettaColor
|
||||
case onboardingthrift.RosettaColor.EnumUnknownRosettaColor(i) =>
|
||||
throw new UnknownThriftEnumException("RosettaColor")
|
||||
}
|
||||
class UnknownThriftEnumException(enumName: String)
|
||||
extends Exception(s"Unknown Thrift Enum Found: ${enumName}")
|
||||
|
||||
class UnsupportedTileCarouselConversionException(UnsupportedTileType: String)
|
||||
extends Exception(s"Unsupported Tile Type Found: ${UnsupportedTileType}")
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.icon
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.icon.BaseHorizonIconBuilder
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.icon.HorizonIcon
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
case class HorizonIconBuilder[-Query <: PipelineQuery, -Candidate <: UniversalNoun[Any]](
|
||||
icon: HorizonIcon)
|
||||
extends BaseHorizonIconBuilder[Query, Candidate] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Option[HorizonIcon] = Some(icon)
|
||||
}
|
|
@ -1,274 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.ad
|
||||
|
||||
import com.twitter.ads.adserver.{thriftscala => ads}
|
||||
import com.twitter.adserver.{thriftscala => adserver}
|
||||
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.TweetClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.ads.AdsCandidate
|
||||
import com.twitter.product_mixer.component_library.model.candidate.ads.AdsTweetCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.tweet.Tweet
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.tweet.TweetDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.tweet.TweetItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.AdMetadataContainer
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.Amplify
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.CallToAction
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.ClickTrackingInfo
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.DcmUrlOverrideType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.DirectSponsorshipType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.DisclaimerIssue
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.DisclaimerPolitical
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.DisclaimerType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.DisclosureType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.DynamicPrerollType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.Earned
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.IndirectSponsorshipType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.Issue
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.LiveTvEvent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.Marketplace
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.MediaInfo
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.NoDisclosure
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.NoSponsorshipSponsorshipType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.Political
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.Preroll
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.PrerollMetadata
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.PromotedMetadata
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.SkAdNetworkData
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.SponsorshipType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.UnknownUrlOverrideType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.VideoVariant
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.timelines.util.AdMetadataContainerSerializer
|
||||
import com.twitter.timelines.util.PrerollMetadataSerializer
|
||||
|
||||
/**
|
||||
* [[AdsCandidateUrtItemBuilder]] takes a [[AdsCandidate]] (with a [[Query]] as additional context)
|
||||
* and converts it into the Product Mixer URT representation, or throws an error.
|
||||
*
|
||||
* Currently, the only supported form for URT representation of the [[AdsCandidate]] is a [[Tweet]],
|
||||
* but in the future it could be expanded to handle other forms of ads.
|
||||
*
|
||||
* @param tweetClientEventInfoBuilder Optionally, provide a ClientEventInfoBuilder for Tweets
|
||||
* that given an AdsTweetCandidate and element of "tweet".
|
||||
* @param tweetDisplayType Should be [[EmphasizedPromotedTweet]] on Profile timelines,
|
||||
* otherwise [[Tweet]]
|
||||
*/
|
||||
case class AdsCandidateUrtItemBuilder[Query <: PipelineQuery](
|
||||
tweetClientEventInfoBuilder: Option[BaseClientEventInfoBuilder[Query, AdsTweetCandidate]] = None,
|
||||
contextualTweetRefBuilder: Option[ContextualTweetRefBuilder[AdsTweetCandidate]] = None,
|
||||
tweetDisplayType: TweetDisplayType = Tweet)
|
||||
extends CandidateUrtEntryBuilder[Query, AdsCandidate, TimelineItem] {
|
||||
|
||||
override def apply(
|
||||
pipelineQuery: Query,
|
||||
candidate: AdsCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): TimelineItem = {
|
||||
candidate match {
|
||||
case tweetCandidate: AdsTweetCandidate =>
|
||||
TweetItem(
|
||||
id = tweetCandidate.id,
|
||||
entryNamespace = TweetItem.PromotedTweetEntryNamespace,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = tweetClientEventInfoBuilder.flatMap(
|
||||
_.apply(
|
||||
pipelineQuery,
|
||||
tweetCandidate,
|
||||
candidateFeatures,
|
||||
Some(TweetClientEventInfoElement))),
|
||||
feedbackActionInfo = None,
|
||||
isPinned = None,
|
||||
entryIdToReplace = None,
|
||||
socialContext = None,
|
||||
highlights = None,
|
||||
innerTombstoneInfo = None,
|
||||
timelinesScoreInfo = None,
|
||||
hasModeratedReplies = None,
|
||||
forwardPivot = None,
|
||||
innerForwardPivot = None,
|
||||
conversationAnnotation = None,
|
||||
promotedMetadata = Some(promotedMetadata(tweetCandidate.adImpression)),
|
||||
displayType = tweetDisplayType,
|
||||
contextualTweetRef = contextualTweetRefBuilder.flatMap(_.apply(tweetCandidate)),
|
||||
prerollMetadata = prerollMetadata(tweetCandidate.adImpression),
|
||||
replyBadge = None,
|
||||
destination = None
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def promotedMetadata(impression: adserver.AdImpression) = {
|
||||
PromotedMetadata(
|
||||
advertiserId = impression.advertiserId,
|
||||
impressionString = impression.impressionString,
|
||||
disclosureType = impression.disclosureType.map(convertDisclosureType),
|
||||
experimentValues = impression.experimentValues.map(_.toMap),
|
||||
promotedTrendId = impression.promotedTrendId.map(_.toLong),
|
||||
promotedTrendName = impression.promotedTrendName,
|
||||
promotedTrendQueryTerm = impression.promotedTrendQueryTerm,
|
||||
promotedTrendDescription = impression.promotedTrendDescription,
|
||||
clickTrackingInfo = impression.clickTrackingInfo.map(convertClickTrackingInfo),
|
||||
adMetadataContainer = adMetadataContainer(impression)
|
||||
)
|
||||
}
|
||||
|
||||
private def convertDisclosureType(
|
||||
disclosureType: adserver.DisclosureType
|
||||
): DisclosureType = disclosureType match {
|
||||
case adserver.DisclosureType.None => NoDisclosure
|
||||
case adserver.DisclosureType.Political => Political
|
||||
case adserver.DisclosureType.Earned => Earned
|
||||
case adserver.DisclosureType.Issue => Issue
|
||||
case _ => throw new UnsupportedDisclosureTypeException(disclosureType)
|
||||
}
|
||||
|
||||
private def convertClickTrackingInfo(
|
||||
clickTracking: adserver.ClickTrackingInfo
|
||||
): ClickTrackingInfo = ClickTrackingInfo(
|
||||
urlParams = clickTracking.urlParams.getOrElse(Map.empty),
|
||||
urlOverride = clickTracking.urlOverride,
|
||||
urlOverrideType = clickTracking.urlOverrideType.map {
|
||||
case adserver.UrlOverrideType.Unknown => UnknownUrlOverrideType
|
||||
case adserver.UrlOverrideType.Dcm => DcmUrlOverrideType
|
||||
case _ => throw new UnsupportedClickTrackingInfoException(clickTracking)
|
||||
}
|
||||
)
|
||||
|
||||
private def prerollMetadata(adImpression: adserver.AdImpression): Option[PrerollMetadata] = {
|
||||
adImpression.serializedPrerollMetadata
|
||||
.flatMap(PrerollMetadataSerializer.deserialize).map { metadata =>
|
||||
PrerollMetadata(
|
||||
metadata.preroll.map(convertPreroll),
|
||||
metadata.videoAnalyticsScribePassthrough
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def adMetadataContainer(
|
||||
adImpression: adserver.AdImpression
|
||||
): Option[AdMetadataContainer] = {
|
||||
adImpression.serializedAdMetadataContainer
|
||||
.flatMap(AdMetadataContainerSerializer.deserialize).map { container =>
|
||||
AdMetadataContainer(
|
||||
removePromotedAttributionForPreroll = container.removePromotedAttributionForPreroll,
|
||||
sponsorshipCandidate = container.sponsorshipCandidate,
|
||||
sponsorshipOrganization = container.sponsorshipOrganization,
|
||||
sponsorshipOrganizationWebsite = container.sponsorshipOrganizationWebsite,
|
||||
sponsorshipType = container.sponsorshipType.map(convertSponsorshipType),
|
||||
disclaimerType = container.disclaimerType.map(convertDisclaimerType),
|
||||
skAdNetworkDataList = container.skAdNetworkDataList.map(convertSkAdNetworkDataList),
|
||||
unifiedCardOverride = container.unifiedCardOverride
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def convertSponsorshipType(
|
||||
sponsorshipType: ads.SponsorshipType
|
||||
): SponsorshipType = sponsorshipType match {
|
||||
case ads.SponsorshipType.Direct => DirectSponsorshipType
|
||||
case ads.SponsorshipType.Indirect => IndirectSponsorshipType
|
||||
case ads.SponsorshipType.NoSponsorship => NoSponsorshipSponsorshipType
|
||||
// Thrift has extras (e.g. Sponsorship4) that are not used in practice
|
||||
case _ => throw new UnsupportedSponsorshipTypeException(sponsorshipType)
|
||||
}
|
||||
|
||||
private def convertDisclaimerType(
|
||||
disclaimerType: ads.DisclaimerType
|
||||
): DisclaimerType = disclaimerType match {
|
||||
case ads.DisclaimerType.Political => DisclaimerPolitical
|
||||
case ads.DisclaimerType.Issue => DisclaimerIssue
|
||||
case _ => throw new UnsupportedDisclaimerTypeException(disclaimerType)
|
||||
}
|
||||
|
||||
private def convertDynamicPrerollType(
|
||||
dynamicPrerollType: ads.DynamicPrerollType
|
||||
): DynamicPrerollType =
|
||||
dynamicPrerollType match {
|
||||
case ads.DynamicPrerollType.Amplify => Amplify
|
||||
case ads.DynamicPrerollType.Marketplace => Marketplace
|
||||
case ads.DynamicPrerollType.LiveTvEvent => LiveTvEvent
|
||||
case _ => throw new UnsupportedDynamicPrerollTypeException(dynamicPrerollType)
|
||||
}
|
||||
|
||||
private def convertMediaInfo(mediaInfo: ads.MediaInfo): MediaInfo = {
|
||||
MediaInfo(
|
||||
uuid = mediaInfo.uuid,
|
||||
publisherId = mediaInfo.publisherId,
|
||||
callToAction = mediaInfo.callToAction.map(convertCallToAction),
|
||||
durationMillis = mediaInfo.durationMillis,
|
||||
videoVariants = mediaInfo.videoVariants.map(convertVideoVariants),
|
||||
advertiserName = mediaInfo.advertiserName,
|
||||
renderAdByAdvertiserName = mediaInfo.renderAdByAdvertiserName,
|
||||
advertiserProfileImageUrl = mediaInfo.advertiserProfileImageUrl
|
||||
)
|
||||
}
|
||||
|
||||
private def convertVideoVariants(videoVariants: Seq[ads.VideoVariant]): Seq[VideoVariant] = {
|
||||
videoVariants.map(videoVariant =>
|
||||
VideoVariant(
|
||||
url = videoVariant.url,
|
||||
contentType = videoVariant.contentType,
|
||||
bitrate = videoVariant.bitrate
|
||||
))
|
||||
}
|
||||
|
||||
private def convertCallToAction(callToAction: ads.CallToAction): CallToAction = {
|
||||
CallToAction(
|
||||
callToActionType = callToAction.callToActionType,
|
||||
url = callToAction.url
|
||||
)
|
||||
}
|
||||
|
||||
private def convertPreroll(
|
||||
preroll: ads.Preroll
|
||||
): Preroll = {
|
||||
Preroll(
|
||||
preroll.prerollId,
|
||||
preroll.dynamicPrerollType.map(convertDynamicPrerollType),
|
||||
preroll.mediaInfo.map(convertMediaInfo)
|
||||
)
|
||||
}
|
||||
|
||||
private def convertSkAdNetworkDataList(
|
||||
skAdNetworkDataList: Seq[ads.SkAdNetworkData]
|
||||
): Seq[SkAdNetworkData] = skAdNetworkDataList.map(sdAdNetwork =>
|
||||
SkAdNetworkData(
|
||||
version = sdAdNetwork.version,
|
||||
srcAppId = sdAdNetwork.srcAppId,
|
||||
dstAppId = sdAdNetwork.dstAppId,
|
||||
adNetworkId = sdAdNetwork.adNetworkId,
|
||||
campaignId = sdAdNetwork.campaignId,
|
||||
impressionTimeInMillis = sdAdNetwork.impressionTimeInMillis,
|
||||
nonce = sdAdNetwork.nonce,
|
||||
signature = sdAdNetwork.signature,
|
||||
fidelityType = sdAdNetwork.fidelityType
|
||||
))
|
||||
}
|
||||
|
||||
class UnsupportedClickTrackingInfoException(clickTrackingInfo: adserver.ClickTrackingInfo)
|
||||
extends UnsupportedOperationException(
|
||||
s"Unsupported ClickTrackingInfo: $clickTrackingInfo"
|
||||
)
|
||||
|
||||
class UnsupportedDisclaimerTypeException(disclaimerType: ads.DisclaimerType)
|
||||
extends UnsupportedOperationException(
|
||||
s"Unsupported DisclaimerType: $disclaimerType"
|
||||
)
|
||||
|
||||
class UnsupportedDisclosureTypeException(disclosureType: adserver.DisclosureType)
|
||||
extends UnsupportedOperationException(
|
||||
s"Unsupported DisclosureType: $disclosureType"
|
||||
)
|
||||
|
||||
class UnsupportedDynamicPrerollTypeException(dynamicPrerollType: ads.DynamicPrerollType)
|
||||
extends UnsupportedOperationException(
|
||||
s"Unsupported DynamicPrerollType: $dynamicPrerollType"
|
||||
)
|
||||
|
||||
class UnsupportedSponsorshipTypeException(sponsorshipType: ads.SponsorshipType)
|
||||
extends UnsupportedOperationException(
|
||||
s"Unsupported SponsorshipType: $sponsorshipType"
|
||||
)
|
|
@ -1,20 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.alert
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.ShowAlertCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.alert.BaseDurationBuilder
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.timelines.configapi.Param
|
||||
import com.twitter.util.Duration
|
||||
|
||||
case class DurationParamBuilder(
|
||||
durationParam: Param[Duration])
|
||||
extends BaseDurationBuilder[PipelineQuery] {
|
||||
|
||||
def apply(
|
||||
query: PipelineQuery,
|
||||
candidate: ShowAlertCandidate,
|
||||
features: FeatureMap
|
||||
): Option[Duration] =
|
||||
Some(query.params(durationParam))
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.alert
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.alert.ShowAlertCandidateUrtItemBuilder.ShowAlertClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.ShowAlertCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.alert.BaseDurationBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.alert.BaseShowAlertColorConfigurationBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.alert.BaseShowAlertDisplayLocationBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.alert.BaseShowAlertIconDisplayInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.alert.BaseShowAlertNavigationMetadataBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.alert.BaseShowAlertUserIdsBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.ShowAlert
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.richtext.BaseRichTextBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.alert.ShowAlertType
|
||||
|
||||
object ShowAlertCandidateUrtItemBuilder {
|
||||
val ShowAlertClientEventInfoElement: String = "showAlert"
|
||||
}
|
||||
|
||||
case class ShowAlertCandidateUrtItemBuilder[-Query <: PipelineQuery](
|
||||
alertType: ShowAlertType,
|
||||
displayLocationBuilder: BaseShowAlertDisplayLocationBuilder[Query],
|
||||
colorConfigBuilder: BaseShowAlertColorConfigurationBuilder[Query],
|
||||
triggerDelayBuilder: Option[BaseDurationBuilder[Query]] = None,
|
||||
displayDurationBuilder: Option[BaseDurationBuilder[Query]] = None,
|
||||
clientEventInfoBuilder: Option[BaseClientEventInfoBuilder[Query, ShowAlertCandidate]] = None,
|
||||
collapseDelayBuilder: Option[BaseDurationBuilder[Query]] = None,
|
||||
userIdsBuilder: Option[BaseShowAlertUserIdsBuilder[Query]] = None,
|
||||
richTextBuilder: Option[BaseRichTextBuilder[Query, ShowAlertCandidate]] = None,
|
||||
iconDisplayInfoBuilder: Option[BaseShowAlertIconDisplayInfoBuilder[Query]] = None,
|
||||
navigationMetadataBuilder: Option[BaseShowAlertNavigationMetadataBuilder[Query]] = None)
|
||||
extends CandidateUrtEntryBuilder[
|
||||
Query,
|
||||
ShowAlertCandidate,
|
||||
ShowAlert
|
||||
] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidate: ShowAlertCandidate,
|
||||
features: FeatureMap,
|
||||
): ShowAlert = ShowAlert(
|
||||
id = candidate.id,
|
||||
sortIndex = None,
|
||||
alertType = alertType,
|
||||
triggerDelay = triggerDelayBuilder.flatMap(_.apply(query, candidate, features)),
|
||||
displayDuration = displayDurationBuilder.flatMap(_.apply(query, candidate, features)),
|
||||
clientEventInfo = clientEventInfoBuilder.flatMap(
|
||||
_.apply(query, candidate, features, Some(ShowAlertClientEventInfoElement))),
|
||||
collapseDelay = collapseDelayBuilder.flatMap(_.apply(query, candidate, features)),
|
||||
userIds = userIdsBuilder.flatMap(_.apply(query, candidate, features)),
|
||||
richText = richTextBuilder.map(_.apply(query, candidate, features)),
|
||||
iconDisplayInfo = iconDisplayInfoBuilder.flatMap(_.apply(query, candidate, features)),
|
||||
displayLocation = displayLocationBuilder(query, candidate, features),
|
||||
colorConfig = colorConfigBuilder(query, candidate, features),
|
||||
navigationMetadata = navigationMetadataBuilder.flatMap(_.apply(query, candidate, features)),
|
||||
)
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.alert
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.ShowAlertCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.alert.BaseShowAlertColorConfigurationBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.alert.ShowAlertColorConfiguration
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
case class StaticShowAlertColorConfigurationBuilder[-Query <: PipelineQuery](
|
||||
configuration: ShowAlertColorConfiguration)
|
||||
extends BaseShowAlertColorConfigurationBuilder[Query] {
|
||||
|
||||
def apply(
|
||||
query: Query,
|
||||
candidate: ShowAlertCandidate,
|
||||
features: FeatureMap
|
||||
): ShowAlertColorConfiguration = configuration
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.alert
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.ShowAlertCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.alert.BaseShowAlertDisplayLocationBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.alert.ShowAlertDisplayLocation
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
case class StaticShowAlertDisplayLocationBuilder[-Query <: PipelineQuery](
|
||||
location: ShowAlertDisplayLocation)
|
||||
extends BaseShowAlertDisplayLocationBuilder[Query] {
|
||||
|
||||
def apply(
|
||||
query: Query,
|
||||
candidate: ShowAlertCandidate,
|
||||
features: FeatureMap
|
||||
): ShowAlertDisplayLocation = location
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.alert
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.ShowAlertCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.alert.BaseShowAlertIconDisplayInfoBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.alert.ShowAlertIconDisplayInfo
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
case class StaticShowAlertIconDisplayInfoBuilder[-Query <: PipelineQuery](
|
||||
iconDisplayInfo: ShowAlertIconDisplayInfo)
|
||||
extends BaseShowAlertIconDisplayInfoBuilder[Query] {
|
||||
|
||||
def apply(
|
||||
query: Query,
|
||||
candidate: ShowAlertCandidate,
|
||||
features: FeatureMap
|
||||
): Option[ShowAlertIconDisplayInfo] = Some(iconDisplayInfo)
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.article
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.article.ArticleCandidateUrtItemBuilder.ArticleClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseArticleCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.social_context.BaseSocialContextBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.article.ArticleDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.article.ArticleItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.article.ArticleSeedType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.article.FollowingListSeed
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object ArticleCandidateUrtItemBuilder {
|
||||
val ArticleClientEventInfoElement: String = "article"
|
||||
}
|
||||
|
||||
case class ArticleCandidateUrtItemBuilder[
|
||||
-Query <: PipelineQuery,
|
||||
Candidate <: BaseArticleCandidate
|
||||
](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, Candidate],
|
||||
articleSeedType: ArticleSeedType = FollowingListSeed,
|
||||
feedbackActionInfoBuilder: Option[
|
||||
BaseFeedbackActionInfoBuilder[Query, Candidate]
|
||||
] = None,
|
||||
displayType: Option[ArticleDisplayType] = None,
|
||||
socialContextBuilder: Option[BaseSocialContextBuilder[Query, Candidate]] = None,
|
||||
) extends CandidateUrtEntryBuilder[Query, Candidate, ArticleItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
articleCandidate: Candidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): ArticleItem = ArticleItem(
|
||||
id = articleCandidate.id,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
articleCandidate,
|
||||
candidateFeatures,
|
||||
Some(ArticleClientEventInfoElement)),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, articleCandidate, candidateFeatures)),
|
||||
displayType = displayType,
|
||||
socialContext =
|
||||
socialContextBuilder.flatMap(_.apply(query, articleCandidate, candidateFeatures)),
|
||||
articleSeedType = articleSeedType
|
||||
)
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.audio_space
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.audio_space.AudioSpaceCandidateUrtItemBuilder.AudioSpaceClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.AudioSpaceCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.audio_space.AudioSpaceItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object AudioSpaceCandidateUrtItemBuilder {
|
||||
val AudioSpaceClientEventInfoElement: String = "audiospace"
|
||||
}
|
||||
|
||||
case class AudioSpaceCandidateUrtItemBuilder[-Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, UniversalNoun[Any]],
|
||||
feedbackActionInfoBuilder: Option[
|
||||
BaseFeedbackActionInfoBuilder[Query, UniversalNoun[Any]]
|
||||
] = None)
|
||||
extends CandidateUrtEntryBuilder[Query, AudioSpaceCandidate, AudioSpaceItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
audioSpaceCandidate: AudioSpaceCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): AudioSpaceItem = AudioSpaceItem(
|
||||
id = audioSpaceCandidate.id,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
audioSpaceCandidate,
|
||||
candidateFeatures,
|
||||
Some(AudioSpaceClientEventInfoElement)),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, audioSpaceCandidate, candidateFeatures))
|
||||
)
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.card
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.card.CardCandidateUtrItemBuilder.CardClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.CardCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseStr
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseUrlBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.card.CardDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.card.CardItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object CardCandidateUtrItemBuilder {
|
||||
val CardClientEventInfoElement: String = "card"
|
||||
}
|
||||
|
||||
case class CardCandidateUtrItemBuilder[-Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, CardCandidate],
|
||||
cardUrlBuilder: BaseStr[Query, CardCandidate],
|
||||
textBuilder: Option[BaseStr[Query, CardCandidate]],
|
||||
subtextBuilder: Option[BaseStr[Query, CardCandidate]],
|
||||
urlBuilder: Option[BaseUrlBuilder[Query, CardCandidate]],
|
||||
cardDisplayType: Option[CardDisplayType],
|
||||
feedbackActionInfoBuilder: Option[
|
||||
BaseFeedbackActionInfoBuilder[Query, CardCandidate],
|
||||
] = None)
|
||||
extends CandidateUrtEntryBuilder[Query, CardCandidate, CardItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
cardCandidate: CardCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): CardItem = CardItem(
|
||||
id = cardCandidate.id,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
cardCandidate,
|
||||
candidateFeatures,
|
||||
Some(CardClientEventInfoElement)),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, cardCandidate, candidateFeatures)),
|
||||
cardUrl = cardUrlBuilder(query, cardCandidate, candidateFeatures),
|
||||
text = textBuilder.map(_.apply(query, cardCandidate, candidateFeatures)),
|
||||
subtext = textBuilder.map(_.apply(query, cardCandidate, candidateFeatures)),
|
||||
url = urlBuilder.map(_.apply(query, cardCandidate, candidateFeatures)),
|
||||
displayType = cardDisplayType
|
||||
)
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.commerce
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.commerce.CommerceProductCandidateUrtItemBuilder.CommerceProductClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.CommerceProductCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.commerce.CommerceProductItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object CommerceProductCandidateUrtItemBuilder {
|
||||
val CommerceProductClientEventInfoElement: String = "commerce-product"
|
||||
}
|
||||
|
||||
case class CommerceProductCandidateUrtItemBuilder[-Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, CommerceProductCandidate],
|
||||
feedbackActionInfoBuilder: Option[BaseFeedbackActionInfoBuilder[Query, CommerceProductCandidate]])
|
||||
extends CandidateUrtEntryBuilder[
|
||||
Query,
|
||||
CommerceProductCandidate,
|
||||
CommerceProductItem
|
||||
] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidate: CommerceProductCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): CommerceProductItem =
|
||||
CommerceProductItem(
|
||||
id = candidate.id,
|
||||
sortIndex = None,
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
candidate,
|
||||
candidateFeatures,
|
||||
Some(CommerceProductClientEventInfoElement)),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, candidate, candidateFeatures))
|
||||
)
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.commerce
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.commerce.CommerceProductGroupCandidateUrtItemBuilder.CommerceProductGroupClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.CommerceProductGroupCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.commerce.CommerceProductGroupItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object CommerceProductGroupCandidateUrtItemBuilder {
|
||||
val CommerceProductGroupClientEventInfoElement: String = "commerce-product-group"
|
||||
}
|
||||
|
||||
case class CommerceProductGroupCandidateUrtItemBuilder[-Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, CommerceProductGroupCandidate],
|
||||
feedbackActionInfoBuilder: Option[
|
||||
BaseFeedbackActionInfoBuilder[Query, CommerceProductGroupCandidate]
|
||||
]) extends CandidateUrtEntryBuilder[
|
||||
Query,
|
||||
CommerceProductGroupCandidate,
|
||||
CommerceProductGroupItem
|
||||
] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidate: CommerceProductGroupCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): CommerceProductGroupItem =
|
||||
CommerceProductGroupItem(
|
||||
id = candidate.id,
|
||||
sortIndex = None,
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
candidate,
|
||||
candidateFeatures,
|
||||
Some(CommerceProductGroupClientEventInfoElement)),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, candidate, candidateFeatures))
|
||||
)
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.event_summary
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.event_summary.EventCandidateUrtItemBuilder.EventClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.trends_events.EventDisplayType
|
||||
import com.twitter.product_mixer.component_library.model.candidate.trends_events.EventImage
|
||||
import com.twitter.product_mixer.component_library.model.candidate.trends_events.EventTimeString
|
||||
import com.twitter.product_mixer.component_library.model.candidate.trends_events.EventTitleFeature
|
||||
import com.twitter.product_mixer.component_library.model.candidate.trends_events.EventUrl
|
||||
import com.twitter.product_mixer.component_library.model.candidate.trends_events.UnifiedEventCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.TimelineItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.event.EventSummaryItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object EventCandidateUrtItemBuilder {
|
||||
val EventClientEventInfoElement = "event"
|
||||
}
|
||||
|
||||
case class EventCandidateUrtItemBuilder[Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, UnifiedEventCandidate],
|
||||
feedbackActionInfoBuilder: Option[BaseFeedbackActionInfoBuilder[Query, UnifiedEventCandidate]] =
|
||||
None)
|
||||
extends CandidateUrtEntryBuilder[Query, UnifiedEventCandidate, TimelineItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidate: UnifiedEventCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): TimelineItem = {
|
||||
EventSummaryItem(
|
||||
id = candidate.id,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query = query,
|
||||
candidate = candidate,
|
||||
candidateFeatures = candidateFeatures,
|
||||
element = Some(EventClientEventInfoElement)
|
||||
),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, candidate, candidateFeatures)),
|
||||
title = candidateFeatures.get(EventTitleFeature),
|
||||
displayType = candidateFeatures.get(EventDisplayType),
|
||||
url = candidateFeatures.get(EventUrl),
|
||||
image = candidateFeatures.getOrElse(EventImage, None),
|
||||
timeString = candidateFeatures.getOrElse(EventTimeString, None)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.generic_summary
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.generic_summary.GenericSummaryActionBuilder.GenericSummaryActionClientEventInfoElement
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseUrlBuilder
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.generic_summary.GenericSummaryAction
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object GenericSummaryActionBuilder {
|
||||
val GenericSummaryActionClientEventInfoElement: String = "genericsummary-action"
|
||||
}
|
||||
|
||||
case class GenericSummaryActionBuilder[-Query <: PipelineQuery, -Candidate <: UniversalNoun[Any]](
|
||||
urlBuilder: BaseUrlBuilder[Query, Candidate],
|
||||
clientEventInfoBuilder: Option[BaseClientEventInfoBuilder[Query, Candidate]] = None) {
|
||||
|
||||
def apply(
|
||||
query: Query,
|
||||
candidate: Candidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): GenericSummaryAction = GenericSummaryAction(
|
||||
url = urlBuilder.apply(query, candidate, candidateFeatures),
|
||||
clientEventInfo = clientEventInfoBuilder.flatMap(
|
||||
_.apply(
|
||||
query,
|
||||
candidate,
|
||||
candidateFeatures,
|
||||
Some(GenericSummaryActionClientEventInfoElement)))
|
||||
)
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.generic_summary
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.generic_summary.GenericSummaryCandidateUrtItemBuilder.GenericSummaryClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.GenericSummaryCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.richtext.BaseRichTextBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.generic_summary.GenericSummaryItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.generic_summary.GenericSummaryItemDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.media.Media
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.promoted.PromotedMetadata
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.util.Time
|
||||
|
||||
object GenericSummaryCandidateUrtItemBuilder {
|
||||
val GenericSummaryClientEventInfoElement: String = "genericsummary"
|
||||
}
|
||||
|
||||
case class GenericSummaryCandidateUrtItemBuilder[-Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, GenericSummaryCandidate],
|
||||
headlineRichTextBuilder: BaseRichTextBuilder[Query, GenericSummaryCandidate],
|
||||
displayType: GenericSummaryItemDisplayType,
|
||||
genericSummaryContextCandidateUrtItemBuilder: Option[
|
||||
GenericSummaryContextBuilder[Query, GenericSummaryCandidate]
|
||||
] = None,
|
||||
genericSummaryActionCandidateUrtItemBuilder: Option[
|
||||
GenericSummaryActionBuilder[Query, GenericSummaryCandidate]
|
||||
] = None,
|
||||
timestamp: Option[Time] = None,
|
||||
userAttributionIds: Option[Seq[Long]] = None,
|
||||
media: Option[Media] = None,
|
||||
promotedMetadata: Option[PromotedMetadata] = None,
|
||||
feedbackActionInfoBuilder: Option[BaseFeedbackActionInfoBuilder[Query, GenericSummaryCandidate]] =
|
||||
None)
|
||||
extends CandidateUrtEntryBuilder[Query, GenericSummaryCandidate, GenericSummaryItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
genericSummaryCandidate: GenericSummaryCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): GenericSummaryItem = GenericSummaryItem(
|
||||
id = genericSummaryCandidate.id,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
genericSummaryCandidate,
|
||||
candidateFeatures,
|
||||
Some(GenericSummaryClientEventInfoElement)),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, genericSummaryCandidate, candidateFeatures)),
|
||||
headline = headlineRichTextBuilder.apply(query, genericSummaryCandidate, candidateFeatures),
|
||||
displayType = displayType,
|
||||
userAttributionIds = userAttributionIds.getOrElse(Seq.empty),
|
||||
media = media,
|
||||
context = genericSummaryContextCandidateUrtItemBuilder.map(
|
||||
_.apply(query, genericSummaryCandidate, candidateFeatures)),
|
||||
timestamp = timestamp,
|
||||
onClickAction = genericSummaryActionCandidateUrtItemBuilder.map(
|
||||
_.apply(query, genericSummaryCandidate, candidateFeatures)),
|
||||
promotedMetadata = promotedMetadata
|
||||
)
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.generic_summary
|
||||
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.richtext.BaseRichTextBuilder
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.icon.HorizonIcon
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.generic_summary.GenericSummaryContext
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
case class GenericSummaryContextBuilder[-Query <: PipelineQuery, -Candidate <: UniversalNoun[Any]](
|
||||
richTextBuilder: BaseRichTextBuilder[Query, Candidate],
|
||||
icon: Option[HorizonIcon] = None) {
|
||||
|
||||
def apply(
|
||||
query: Query,
|
||||
candidate: Candidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): GenericSummaryContext = GenericSummaryContext(
|
||||
richTextBuilder.apply(query, candidate, candidateFeatures),
|
||||
icon
|
||||
)
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.icon_label
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.icon_label.IconLabelCandidateUrtItemBuilder.IconLabelClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.LabelCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.richtext.BaseRichTextBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.icon.HorizonIcon
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.icon_label.IconLabelItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.richtext.RichTextEntity
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object IconLabelCandidateUrtItemBuilder {
|
||||
val IconLabelClientEventInfoElement: String = "iconlabel"
|
||||
}
|
||||
|
||||
case class IconLabelCandidateUrtItemBuilder[-Query <: PipelineQuery, Candidate <: LabelCandidate](
|
||||
richTextBuilder: BaseRichTextBuilder[Query, Candidate],
|
||||
icon: Option[HorizonIcon] = None,
|
||||
entities: Option[List[RichTextEntity]] = None,
|
||||
clientEventInfoBuilder: Option[BaseClientEventInfoBuilder[Query, Candidate]] = None,
|
||||
feedbackActionInfoBuilder: Option[BaseFeedbackActionInfoBuilder[Query, Candidate]] = None)
|
||||
extends CandidateUrtEntryBuilder[Query, Candidate, IconLabelItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
labelCandidate: Candidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): IconLabelItem =
|
||||
IconLabelItem(
|
||||
id = labelCandidate.id.toString,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder.flatMap(
|
||||
_.apply(query, labelCandidate, candidateFeatures, Some(IconLabelClientEventInfoElement))),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, labelCandidate, candidateFeatures)),
|
||||
text = richTextBuilder(query, labelCandidate, candidateFeatures),
|
||||
icon = icon,
|
||||
)
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.message
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.message.CompactPromptCandidateUrtItemStringCenterBuilder.CompactPromptClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.CompactPromptCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseStr
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.richtext.BaseRichTextBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.CompactPromptMessageContent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.MessagePromptItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object CompactPromptCandidateUrtItemStringCenterBuilder {
|
||||
val CompactPromptClientEventInfoElement: String = "message"
|
||||
}
|
||||
|
||||
case class CompactPromptCandidateUrtItemStringCenterBuilder[-Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, CompactPromptCandidate],
|
||||
feedbackActionInfoBuilder: Option[
|
||||
BaseFeedbackActionInfoBuilder[Query, CompactPromptCandidate]
|
||||
] = None,
|
||||
headerTextBuilder: BaseStr[Query, CompactPromptCandidate],
|
||||
bodyTextBuilder: Option[BaseStr[Query, CompactPromptCandidate]] = None,
|
||||
headerRichTextBuilder: Option[BaseRichTextBuilder[Query, CompactPromptCandidate]] = None,
|
||||
bodyRichTextBuilder: Option[BaseRichTextBuilder[Query, CompactPromptCandidate]] = None)
|
||||
extends CandidateUrtEntryBuilder[Query, CompactPromptCandidate, MessagePromptItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
compactPromptCandidate: CompactPromptCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): MessagePromptItem =
|
||||
MessagePromptItem(
|
||||
id = compactPromptCandidate.id.toString,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
compactPromptCandidate,
|
||||
candidateFeatures,
|
||||
Some(CompactPromptClientEventInfoElement)),
|
||||
feedbackActionInfo = feedbackActionInfoBuilder.flatMap(
|
||||
_.apply(query, compactPromptCandidate, candidateFeatures)),
|
||||
isPinned = None,
|
||||
content = CompactPromptMessageContent(
|
||||
headerText = headerTextBuilder.apply(query, compactPromptCandidate, candidateFeatures),
|
||||
bodyText = bodyTextBuilder.map(_.apply(query, compactPromptCandidate, candidateFeatures)),
|
||||
primaryButtonAction = None,
|
||||
secondaryButtonAction = None,
|
||||
action = None,
|
||||
headerRichText =
|
||||
headerRichTextBuilder.map(_.apply(query, compactPromptCandidate, candidateFeatures)),
|
||||
bodyRichText =
|
||||
bodyRichTextBuilder.map(_.apply(query, compactPromptCandidate, candidateFeatures))
|
||||
),
|
||||
impressionCallbacks = None
|
||||
)
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.message
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.message.InlinePromptCandidateUrtItemStringCenterBuilder.InlinePromptClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.InlinePromptCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseStr
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.richtext.BaseRichTextBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.social_context.BaseSocialContextBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.InlinePromptMessageContent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.MessagePromptItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object InlinePromptCandidateUrtItemStringCenterBuilder {
|
||||
val InlinePromptClientEventInfoElement: String = "message"
|
||||
}
|
||||
|
||||
case class InlinePromptCandidateUrtItemStringCenterBuilder[-Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, InlinePromptCandidate],
|
||||
feedbackActionInfoBuilder: Option[
|
||||
BaseFeedbackActionInfoBuilder[Query, InlinePromptCandidate]
|
||||
] = None,
|
||||
headerTextBuilder: BaseStr[Query, InlinePromptCandidate],
|
||||
bodyTextBuilder: Option[BaseStr[Query, InlinePromptCandidate]] = None,
|
||||
headerRichTextBuilder: Option[BaseRichTextBuilder[Query, InlinePromptCandidate]] = None,
|
||||
bodyRichTextBuilder: Option[BaseRichTextBuilder[Query, InlinePromptCandidate]] = None,
|
||||
primaryMessageTextActionBuilder: Option[
|
||||
MessageTextActionBuilder[Query, InlinePromptCandidate]
|
||||
] = None,
|
||||
secondaryMessageTextActionBuilder: Option[
|
||||
MessageTextActionBuilder[Query, InlinePromptCandidate]
|
||||
] = None,
|
||||
socialContextBuilder: Option[BaseSocialContextBuilder[Query, InlinePromptCandidate]] = None,
|
||||
userFacePileBuilder: Option[
|
||||
UserFacePileBuilder
|
||||
] = None)
|
||||
extends CandidateUrtEntryBuilder[Query, InlinePromptCandidate, MessagePromptItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
inlinePromptCandidate: InlinePromptCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): MessagePromptItem =
|
||||
MessagePromptItem(
|
||||
id = inlinePromptCandidate.id,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
inlinePromptCandidate,
|
||||
candidateFeatures,
|
||||
Some(InlinePromptClientEventInfoElement)),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, inlinePromptCandidate, candidateFeatures)),
|
||||
isPinned = None,
|
||||
content = InlinePromptMessageContent(
|
||||
headerText = headerTextBuilder.apply(query, inlinePromptCandidate, candidateFeatures),
|
||||
bodyText = bodyTextBuilder.map(_.apply(query, inlinePromptCandidate, candidateFeatures)),
|
||||
primaryButtonAction = primaryMessageTextActionBuilder.map(
|
||||
_.apply(query, inlinePromptCandidate, candidateFeatures)),
|
||||
secondaryButtonAction = secondaryMessageTextActionBuilder.map(
|
||||
_.apply(query, inlinePromptCandidate, candidateFeatures)),
|
||||
headerRichText =
|
||||
headerRichTextBuilder.map(_.apply(query, inlinePromptCandidate, candidateFeatures)),
|
||||
bodyRichText =
|
||||
bodyRichTextBuilder.map(_.apply(query, inlinePromptCandidate, candidateFeatures)),
|
||||
socialContext =
|
||||
socialContextBuilder.flatMap(_.apply(query, inlinePromptCandidate, candidateFeatures)),
|
||||
userFacepile = userFacePileBuilder.map(_.apply())
|
||||
),
|
||||
impressionCallbacks = None
|
||||
)
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.message
|
||||
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseStr
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.MessageAction
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.MessageTextAction
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.Callback
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.ClientEventInfo
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object MessageTextActionBuilder {
|
||||
val MessageTextActionClientEventInfoElement: String = "message-text-action"
|
||||
}
|
||||
|
||||
case class MessageTextActionBuilder[-Query <: PipelineQuery, -Candidate <: UniversalNoun[Any]](
|
||||
textBuilder: BaseStr[Query, Candidate],
|
||||
dismissOnClick: Boolean,
|
||||
url: Option[String] = None,
|
||||
clientEventInfo: Option[ClientEventInfo] = None,
|
||||
onClickCallbacks: Option[List[Callback]] = None) {
|
||||
|
||||
def apply(
|
||||
query: Query,
|
||||
candidate: Candidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): MessageTextAction = MessageTextAction(
|
||||
text = textBuilder(query, candidate, candidateFeatures),
|
||||
action = MessageAction(
|
||||
dismissOnClick,
|
||||
url,
|
||||
clientEventInfo,
|
||||
onClickCallbacks
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.message
|
||||
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.MessageActionType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.MessageTextAction
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.UserFacepile
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.message.UserFacepileDisplayType
|
||||
|
||||
case class UserFacePileBuilder(
|
||||
userIds: Seq[Long],
|
||||
featuredUserIds: Seq[Long],
|
||||
action: Option[MessageTextAction],
|
||||
actionType: Option[MessageActionType],
|
||||
displaysFeaturingText: Option[Boolean],
|
||||
displayType: Option[UserFacepileDisplayType]) {
|
||||
|
||||
def apply(): UserFacepile = UserFacepile(
|
||||
userIds = userIds,
|
||||
featuredUserIds = featuredUserIds,
|
||||
action = action,
|
||||
actionType = actionType,
|
||||
displaysFeaturingText = displaysFeaturingText,
|
||||
displayType = displayType
|
||||
)
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.moment
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.moment.MomentAnnotationCandidateUrtItemBuilder.MomentAnnotationItemClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.MomentAnnotationCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.richtext.BaseRichTextBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.moment.MomentAnnotationItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object MomentAnnotationCandidateUrtItemBuilder {
|
||||
val MomentAnnotationItemClientEventInfoElement = "metadata"
|
||||
}
|
||||
|
||||
case class MomentAnnotationCandidateUrtItemBuilder[Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, MomentAnnotationCandidate],
|
||||
annotationTextRichTextBuilder: BaseRichTextBuilder[Query, MomentAnnotationCandidate],
|
||||
annotationHeaderRichTextBuilder: BaseRichTextBuilder[Query, MomentAnnotationCandidate],
|
||||
feedbackActionInfoBuilder: Option[
|
||||
BaseFeedbackActionInfoBuilder[Query, MomentAnnotationCandidate]
|
||||
] = None,
|
||||
) extends CandidateUrtEntryBuilder[Query, MomentAnnotationCandidate, MomentAnnotationItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidate: MomentAnnotationCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): MomentAnnotationItem = MomentAnnotationItem(
|
||||
id = candidate.id,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
candidate,
|
||||
candidateFeatures,
|
||||
Some(MomentAnnotationItemClientEventInfoElement)),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, candidate, candidateFeatures)),
|
||||
isPinned = None,
|
||||
text =
|
||||
candidate.text.map(_ => annotationTextRichTextBuilder(query, candidate, candidateFeatures)),
|
||||
header = candidate.header.map(_ =>
|
||||
annotationHeaderRichTextBuilder(query, candidate, candidateFeatures)),
|
||||
)
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.relevance_prompt
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.relevance_prompt.RelevancePromptCandidateUrtItemStringCenterBuilder.RelevancePromptClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.RelevancePromptCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseStr
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.prompt.PromptItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.prompt.RelevancePromptContent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.prompt.RelevancePromptDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.prompt.RelevancePromptFollowUpFeedbackType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.metadata.Callback
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object RelevancePromptCandidateUrtItemStringCenterBuilder {
|
||||
val RelevancePromptClientEventInfoElement: String = "relevance_prompt"
|
||||
}
|
||||
|
||||
case class RelevancePromptCandidateUrtItemStringCenterBuilder[-Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, RelevancePromptCandidate],
|
||||
titleTextBuilder: BaseStr[Query, RelevancePromptCandidate],
|
||||
confirmationTextBuilder: BaseStr[Query, RelevancePromptCandidate],
|
||||
isRelevantTextBuilder: BaseStr[Query, RelevancePromptCandidate],
|
||||
notRelevantTextBuilder: BaseStr[Query, RelevancePromptCandidate],
|
||||
displayType: RelevancePromptDisplayType,
|
||||
isRelevantCallback: Callback,
|
||||
notRelevantCallback: Callback,
|
||||
isRelevantFollowUp: Option[RelevancePromptFollowUpFeedbackType] = None,
|
||||
notRelevantFollowUp: Option[RelevancePromptFollowUpFeedbackType] = None,
|
||||
impressionCallbacks: Option[List[Callback]] = None)
|
||||
extends CandidateUrtEntryBuilder[Query, RelevancePromptCandidate, PromptItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
relevancePromptCandidate: RelevancePromptCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): PromptItem =
|
||||
PromptItem(
|
||||
id = relevancePromptCandidate.id,
|
||||
sortIndex = None,
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
relevancePromptCandidate,
|
||||
candidateFeatures,
|
||||
Some(RelevancePromptClientEventInfoElement)),
|
||||
feedbackActionInfo = None,
|
||||
content = RelevancePromptContent(
|
||||
title = titleTextBuilder(query, relevancePromptCandidate, candidateFeatures),
|
||||
confirmation = confirmationTextBuilder(query, relevancePromptCandidate, candidateFeatures),
|
||||
isRelevantText = isRelevantTextBuilder(query, relevancePromptCandidate, candidateFeatures),
|
||||
notRelevantText =
|
||||
notRelevantTextBuilder(query, relevancePromptCandidate, candidateFeatures),
|
||||
isRelevantCallback = isRelevantCallback,
|
||||
notRelevantCallback = notRelevantCallback,
|
||||
displayType = displayType,
|
||||
isRelevantFollowUp = isRelevantFollowUp,
|
||||
notRelevantFollowUp = notRelevantFollowUp,
|
||||
),
|
||||
impressionCallbacks = impressionCallbacks
|
||||
)
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.suggestion
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.suggestion.SpellingSuggestionCandidateUrtItemBuilder.SpellingItemClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.suggestion.SpellingSuggestionCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.suggestion.SpellingItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object SpellingSuggestionCandidateUrtItemBuilder {
|
||||
val SpellingItemClientEventInfoElement: String = "spelling"
|
||||
}
|
||||
|
||||
case class SpellingSuggestionCandidateUrtItemBuilder[Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, SpellingSuggestionCandidate],
|
||||
feedbackActionInfoBuilder: Option[
|
||||
BaseFeedbackActionInfoBuilder[Query, SpellingSuggestionCandidate]
|
||||
] = None,
|
||||
) extends CandidateUrtEntryBuilder[Query, SpellingSuggestionCandidate, SpellingItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidate: SpellingSuggestionCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): SpellingItem = SpellingItem(
|
||||
id = candidate.id,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
candidate,
|
||||
candidateFeatures,
|
||||
Some(SpellingItemClientEventInfoElement)),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, candidate, candidateFeatures)),
|
||||
textResult = candidate.textResult,
|
||||
spellingActionType = candidate.spellingActionType,
|
||||
originalQuery = candidate.originalQuery
|
||||
)
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.tile
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.tile.TileCandidateUrtItemBuilder.TopicTileClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.PromptCarouselTileCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.tile.StandardTileContent
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.tile.TileItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object TileCandidateUrtItemBuilder {
|
||||
val TopicTileClientEventInfoElement: String = "tile"
|
||||
}
|
||||
|
||||
case class TileCandidateUrtItemBuilder[-Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, PromptCarouselTileCandidate],
|
||||
feedbackActionInfoBuilder: Option[
|
||||
BaseFeedbackActionInfoBuilder[Query, PromptCarouselTileCandidate]
|
||||
] = None)
|
||||
extends CandidateUrtEntryBuilder[Query, PromptCarouselTileCandidate, TileItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
tileCandidate: PromptCarouselTileCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): TileItem = TileItem(
|
||||
id = tileCandidate.id,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
tileCandidate,
|
||||
candidateFeatures,
|
||||
Some(TopicTileClientEventInfoElement)),
|
||||
title = "", //This data is ignored do
|
||||
supportingText = "",
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, tileCandidate, candidateFeatures)),
|
||||
image = None,
|
||||
url = None,
|
||||
content = StandardTileContent(
|
||||
title = "",
|
||||
supportingText = "",
|
||||
badge = None
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.topic
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TopicCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.timelines.configapi.FSEnumParam
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.topic.BasicTopicDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.topic.PillTopicDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.topic.NoIconTopicDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.topic.PillWithoutActionIconDisplayType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.topic.TopicDisplayType
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.topic.BaseTopicDisplayTypeBuilder
|
||||
|
||||
object TopicCandidateDisplayType extends Enumeration {
|
||||
type TopicDisplayType = Value
|
||||
|
||||
val Basic = Value
|
||||
val Pill = Value
|
||||
val NoIcon = Value
|
||||
val PillWithoutActionIcon = Value
|
||||
}
|
||||
|
||||
case class ParamTopicDisplayTypeBuilder(
|
||||
displayTypeParam: FSEnumParam[TopicCandidateDisplayType.type])
|
||||
extends BaseTopicDisplayTypeBuilder[PipelineQuery, TopicCandidate] {
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidate: TopicCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): Option[TopicDisplayType] = {
|
||||
val displayType = query.params(displayTypeParam)
|
||||
displayType match {
|
||||
case TopicCandidateDisplayType.Basic => Some(BasicTopicDisplayType)
|
||||
case TopicCandidateDisplayType.Pill => Some(PillTopicDisplayType)
|
||||
case TopicCandidateDisplayType.NoIcon =>
|
||||
Some(NoIconTopicDisplayType)
|
||||
case TopicCandidateDisplayType.PillWithoutActionIcon => Some(PillWithoutActionIconDisplayType)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.topic
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TopicCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.topic.BasicTopicFunctionalityType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.topic.PivotTopicFunctionalityType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.topic.RecommendationTopicFunctionalityType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.topic.TopicFunctionalityType
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.topic.BaseTopicFunctionalityTypeBuilder
|
||||
import com.twitter.timelines.configapi.FSEnumParam
|
||||
|
||||
object TopicFunctionalityTypeParamValue extends Enumeration {
|
||||
type TopicFunctionalityType = Value
|
||||
|
||||
val Basic = Value
|
||||
val Pivot = Value
|
||||
val Recommendation = Value
|
||||
}
|
||||
|
||||
case class ParamTopicFunctionalityTypeBuilder(
|
||||
functionalityTypeParam: FSEnumParam[TopicFunctionalityTypeParamValue.type])
|
||||
extends BaseTopicFunctionalityTypeBuilder[PipelineQuery, TopicCandidate] {
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidate: TopicCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): Option[TopicFunctionalityType] = {
|
||||
val functionalityType = query.params(functionalityTypeParam)
|
||||
functionalityType match {
|
||||
case TopicFunctionalityTypeParamValue.Basic => Some(BasicTopicFunctionalityType)
|
||||
case TopicFunctionalityTypeParamValue.Pivot => Some(PivotTopicFunctionalityType)
|
||||
case TopicFunctionalityTypeParamValue.Recommendation =>
|
||||
Some(RecommendationTopicFunctionalityType)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.topic
|
||||
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTopicCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.topic.BaseTopicDisplayTypeBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.topic.TopicDisplayType
|
||||
|
||||
case class StaticTopicDisplayTypeBuilder(
|
||||
displayType: TopicDisplayType)
|
||||
extends BaseTopicDisplayTypeBuilder[PipelineQuery, BaseTopicCandidate] {
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidate: BaseTopicCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): Option[TopicDisplayType] = Some(displayType)
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.topic
|
||||
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTopicCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.topic.BaseTopicFunctionalityTypeBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.topic.TopicFunctionalityType
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
case class StaticTopicFunctionalityTypeBuilder(
|
||||
functionalityType: TopicFunctionalityType)
|
||||
extends BaseTopicFunctionalityTypeBuilder[PipelineQuery, BaseTopicCandidate] {
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidate: BaseTopicCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): Option[TopicFunctionalityType] = Some(functionalityType)
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.topic
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.topic.TopicCandidateUrtItemBuilder.TopicClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTopicCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.topic.BaseTopicDisplayTypeBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.item.topic.BaseTopicFunctionalityTypeBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.topic.TopicItem
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
object TopicCandidateUrtItemBuilder {
|
||||
val TopicClientEventInfoElement: String = "topic"
|
||||
}
|
||||
|
||||
case class TopicCandidateUrtItemBuilder[-Query <: PipelineQuery, Candidate <: BaseTopicCandidate](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, Candidate],
|
||||
topicFunctionalityTypeBuilder: Option[BaseTopicFunctionalityTypeBuilder[Query, Candidate]] = None,
|
||||
topicDisplayTypeBuilder: Option[BaseTopicDisplayTypeBuilder[Query, Candidate]] = None,
|
||||
feedbackActionInfoBuilder: Option[
|
||||
BaseFeedbackActionInfoBuilder[Query, Candidate]
|
||||
] = None)
|
||||
extends CandidateUrtEntryBuilder[Query, Candidate, TopicItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
topicCandidate: Candidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): TopicItem =
|
||||
TopicItem(
|
||||
id = topicCandidate.id,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
topicCandidate,
|
||||
candidateFeatures,
|
||||
Some(TopicClientEventInfoElement)),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, topicCandidate, candidateFeatures)),
|
||||
topicFunctionalityType =
|
||||
topicFunctionalityTypeBuilder.flatMap(_.apply(query, topicCandidate, candidateFeatures)),
|
||||
topicDisplayType =
|
||||
topicDisplayTypeBuilder.flatMap(_.apply(query, topicCandidate, candidateFeatures))
|
||||
)
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package com.twitter.product_mixer.component_library.decorator.urt.builder.item.topic
|
||||
|
||||
import com.twitter.product_mixer.component_library.decorator.urt.builder.item.topic.TopicCandidateUrtItemBuilder.TopicClientEventInfoElement
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TopicCandidate
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.CandidateUrtEntryBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseClientEventInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseFeedbackActionInfoBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.decorator.urt.builder.metadata.BaseUrlBuilder
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.vertical_grid_item.VerticalGridItem
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.vertical_grid_item.VerticalGridItemTileStyle
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.vertical_grid_item.VerticalGridItemTopicFunctionalityType
|
||||
import com.twitter.product_mixer.core.model.marshalling.response.urt.item.vertical_grid_item.VerticalGridItemTopicTile
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
|
||||
case class VerticalGridTopicCandidateUrtItemBuilder[-Query <: PipelineQuery](
|
||||
clientEventInfoBuilder: BaseClientEventInfoBuilder[Query, TopicCandidate],
|
||||
verticalGridItemTopicFunctionalityType: VerticalGridItemTopicFunctionalityType,
|
||||
verticalGridItemTileStyle: VerticalGridItemTileStyle,
|
||||
urlBuilder: Option[BaseUrlBuilder[Query, TopicCandidate]] = None,
|
||||
feedbackActionInfoBuilder: Option[
|
||||
BaseFeedbackActionInfoBuilder[Query, TopicCandidate]
|
||||
] = None)
|
||||
extends CandidateUrtEntryBuilder[Query, TopicCandidate, VerticalGridItem] {
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
topicCandidate: TopicCandidate,
|
||||
candidateFeatures: FeatureMap
|
||||
): VerticalGridItem = {
|
||||
VerticalGridItemTopicTile(
|
||||
id = topicCandidate.id,
|
||||
sortIndex = None, // Sort indexes are automatically set in the domain marshaller phase
|
||||
clientEventInfo = clientEventInfoBuilder(
|
||||
query,
|
||||
topicCandidate,
|
||||
candidateFeatures,
|
||||
Some(TopicClientEventInfoElement)),
|
||||
feedbackActionInfo =
|
||||
feedbackActionInfoBuilder.flatMap(_.apply(query, topicCandidate, candidateFeatures)),
|
||||
style = Some(verticalGridItemTileStyle),
|
||||
functionalityType = Some(verticalGridItemTopicFunctionalityType),
|
||||
url = urlBuilder.map(_.apply(query, topicCandidate, candidateFeatures))
|
||||
)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue