package com.twitter.home_mixer.functional_component.feature_hydrator import com.twitter.home_mixer.model.HomeFeatures.FavoritedByUserIdsFeature import com.twitter.home_mixer.model.HomeFeatures.FollowedByUserIdsFeature import com.twitter.home_mixer.model.HomeFeatures.SGSValidFollowedByUserIdsFeature import com.twitter.home_mixer.model.HomeFeatures.SGSValidLikedByUserIdsFeature import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate import com.twitter.product_mixer.core.feature.Feature import com.twitter.product_mixer.core.feature.featuremap.FeatureMap import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder import com.twitter.product_mixer.core.functional_component.feature_hydrator.BulkCandidateFeatureHydrator import com.twitter.product_mixer.core.model.common.CandidateWithFeatures import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier import com.twitter.product_mixer.core.pipeline.PipelineQuery import com.twitter.product_mixer.core.util.OffloadFuturePools import com.twitter.socialgraph.{thriftscala => sg} import com.twitter.stitch.Stitch import com.twitter.stitch.socialgraph.SocialGraph import javax.inject.Inject import javax.inject.Singleton /** * This hydrator takes liked-by and followed-by user ids and checks via SGS that the viewer is * following the engager, that the viewer is not blocking the engager, that the engager is not * blocking the viewer, and that the viewer has not muted the engager. */ @Singleton class SGSValidSocialContextFeatureHydrator @Inject() ( socialGraph: SocialGraph) extends BulkCandidateFeatureHydrator[PipelineQuery, TweetCandidate] { override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier("SGSValidSocialContext") override val features: Set[Feature[_, _]] = Set( SGSValidFollowedByUserIdsFeature, SGSValidLikedByUserIdsFeature ) private val MaxCountUsers = 10 override def apply( query: PipelineQuery, candidates: Seq[CandidateWithFeatures[TweetCandidate]] ): Stitch[Seq[FeatureMap]] = OffloadFuturePools.offloadStitch { val allSocialContextUserIds = candidates.flatMap { candidate => candidate.features.getOrElse(FavoritedByUserIdsFeature, Nil).take(MaxCountUsers) ++ candidate.features.getOrElse(FollowedByUserIdsFeature, Nil).take(MaxCountUsers) }.distinct getValidUserIds(query.getRequiredUserId, allSocialContextUserIds).map { validUserIds => candidates.map { candidate => val sgsFilteredLikedByUserIds = candidate.features .getOrElse(FavoritedByUserIdsFeature, Nil).take(MaxCountUsers) .filter(validUserIds.contains) val sgsFilteredFollowedByUserIds = candidate.features .getOrElse(FollowedByUserIdsFeature, Nil).take(MaxCountUsers) .filter(validUserIds.contains) FeatureMapBuilder() .add(SGSValidFollowedByUserIdsFeature, sgsFilteredFollowedByUserIds) .add(SGSValidLikedByUserIdsFeature, sgsFilteredLikedByUserIds) .build() } } } private def getValidUserIds( viewerId: Long, socialProofUserIds: Seq[Long] ): Stitch[Seq[Long]] = { if (socialProofUserIds.nonEmpty) { val request = sg.IdsRequest( relationships = Seq( sg.SrcRelationship( viewerId, sg.RelationshipType.Following, targets = Some(socialProofUserIds), hasRelationship = true), sg.SrcRelationship( viewerId, sg.RelationshipType.Blocking, targets = Some(socialProofUserIds), hasRelationship = false), sg.SrcRelationship( viewerId, sg.RelationshipType.BlockedBy, targets = Some(socialProofUserIds), hasRelationship = false), sg.SrcRelationship( viewerId, sg.RelationshipType.Muting, targets = Some(socialProofUserIds), hasRelationship = false) ), pageRequest = Some(sg.PageRequest(selectAll = Some(true))) ) socialGraph.ids(request).map(_.ids) } else Stitch.Nil } }