Delete product-mixer directory

This commit is contained in:
dogemanttv 2024-01-10 17:07:46 -06:00 committed by GitHub
parent 61c1dd624f
commit 7e7475fc05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1378 changed files with 0 additions and 59681 deletions

View File

@ -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.

View File

@ -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)
}
}

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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",
],
)

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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",
],
)

View File

@ -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",
],
)

View File

@ -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()
}
}

View File

@ -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",
],
)

View File

@ -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()
}
}

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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",
],
)

View File

@ -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)
}
}
}

View File

@ -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",
],
)

View File

@ -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")
}
}
}

View File

@ -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",
],
)

View File

@ -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])

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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",
],
)

View File

@ -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
}
}
}

View File

@ -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",
],
)

View File

@ -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)
}
}
}
}

View File

@ -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",
],
)

View File

@ -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)
}
}

View File

@ -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",
],
)

View File

@ -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")
}
}
}

View File

@ -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",
],
)

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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",
],
)

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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",
],
)

View File

@ -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
)
}
}

View File

@ -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",
],
)

View File

@ -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)
}
}
}

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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",
],
)

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}
}

View File

@ -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",
],
)

View File

@ -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)
}
}

View File

@ -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",
],
)

View File

@ -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)
}
}

View File

@ -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",
],
)

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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)))
}

View File

@ -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
)
}
}

View File

@ -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")

View File

@ -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())
}
}

View File

@ -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))

View File

@ -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
)
}

View File

@ -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")
))
}
}

View File

@ -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}")
}

View File

@ -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)
}

View File

@ -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"
)

View File

@ -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))
}

View File

@ -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)),
)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
)
}

View File

@ -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))
)
}

View File

@ -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
)
}

View File

@ -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))
)
}

View File

@ -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))
)
}

View File

@ -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)
)
}
}

View File

@ -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)))
)
}

View File

@ -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
)
}

View File

@ -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
)
}

View File

@ -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,
)
}

View File

@ -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
)
}

View File

@ -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
)
}

View File

@ -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
)
)
}

View File

@ -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
)
}

View File

@ -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)),
)
}

View File

@ -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
)
}

View File

@ -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
)
}

View File

@ -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
)
)
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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))
)
}

View File

@ -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