def apply()

in visibilitylib/src/main/scala/com/twitter/visibility/interfaces/conversations/TombstoneVisibilityLibrary.scala [299:595]


  def apply(request: TombstoneVisibilityRequest): Stitch[TombstoneVisibilityResponse] = {

    val moderationFeatures = new ModerationFeatures(
      moderationSource = request.moderatedTweetIds.contains,
      statsReceiver = statsReceiver
    )

    val userSource = UserSource.fromFunction({
      case (userId, _) =>
        request.authorMap
          .get(userId)
          .map(Stitch.value).getOrElse(Stitch.NotFound)
    })

    val authorFeatures = new AuthorFeatures(userSource, statsReceiver)
    val viewerFeatures = new ViewerFeatures(userSource, statsReceiver)

    val languageTag = request.viewerContext.requestCountryCode.getOrElse(En)
    val firstRound: Seq[(GetTweetFieldsResult, Option[TombstoneType])] = request.tweets.map {
      case (gtfr, safetyLevel) =>
        val actionStats = statsReceiver
          .scope("action")
          .scope(safetyLevel.map(_.toString().toLowerCase()).getOrElse("unknown_safety_level"))
        toAction(gtfr.tweetResult, actionStats) match {
          case Some(action) =>
            (gtfr, Some(TombstoneType(gtfr.tweetId, gtfr.tweetId, action)))

          case None =>
            val quotedTweetId: Option[Long] = gtfr.tweetResult match {
              case TweetFieldsResultState.Found(TweetFieldsResultFound(tweet, _, _)) =>
                tweet.quotedTweet.map(_.tweetId)
              case _ => None
            }

            (quotedTweetId, gtfr.quotedTweetResult) match {
              case (Some(quotedTweetId), Some(tfrs)) =>
                val qtActionStats = actionStats.scope("quoted")
                toAction(tfrs, qtActionStats) match {
                  case None =>
                    (gtfr, None)

                  case Some(action) =>
                    (gtfr, Some(TombstoneType(gtfr.tweetId, quotedTweetId, action)))
                }

              case _ =>
                (gtfr, None)
            }
        }
    }

    val (firstRoundActions, secondRoundInput) = firstRound.partition {
      case (_, Some(tombstoneType)) =>
        !tombstoneType.isInnerTombstone
      case (_, None) => false
    }

    def invokeVisibilityLibrary(tweetId: Long, author: User): Stitch[Action] = {
      visibilityLibrary
        .runRuleEngine(
          ContentId.TweetId(tweetId),
          visibilityLibrary.featureMapBuilder(
            Seq(
              viewerFeatures.forViewerContext(request.viewerContext),
              moderationFeatures.forTweetId(tweetId),
              authorFeatures.forAuthor(author),
              relationshipFeatures
                .forAuthor(author, request.viewerContext.userId),
              _.withConstantFeature(TweetId, tweetId),
              _.withConstantFeature(FocalTweetId, request.focalTweetId)
            )
          ),
          request.viewerContext,
          Tombstoning
        ).map(_.verdict)
    }

    val secondRoundActions: Stitch[Seq[(GetTweetFieldsResult, Option[TombstoneType])]] =
      Stitch.traverse(secondRoundInput) {
        case (gtfr: GetTweetFieldsResult, firstRoundTombstone: Option[TombstoneType]) =>
          val secondRoundTombstone: Stitch[Option[TombstoneType]] = gtfr.tweetResult match {
            case TweetFieldsResultState.Found(TweetFieldsResultFound(tweet, _, _)) =>
              val tweetId = tweet.id

              tweet.coreData
                .flatMap { coreData => request.authorMap.get(coreData.userId) } match {
                case Some(author) =>
                  invokeVisibilityLibrary(tweetId, author).flatMap {
                    case Allow =>
                      val quotedTweetId = tweet.quotedTweet.map(_.tweetId)
                      val quotedTweetAuthor = tweet.quotedTweet.flatMap { qt =>
                        request.authorMap.get(qt.userId)
                      }

                      (quotedTweetId, quotedTweetAuthor) match {
                        case (Some(quotedTweetId), Some(quotedTweetAuthor)) =>
                          invokeVisibilityLibrary(quotedTweetId, quotedTweetAuthor).flatMap {
                            case Allow =>
                              Stitch.None

                            case reason =>
                              Stitch.value(Some(TombstoneType(tweetId, quotedTweetId, reason)))
                          }

                        case _ =>
                          Stitch.None
                      }

                    case reason =>
                      Stitch.value(Some(TombstoneType(tweetId, tweetId, reason)))
                  }

                case None =>
                  Stitch.None
              }

            case _ =>
              Stitch.None
          }

          secondRoundTombstone.map { opt => opt.orElse(firstRoundTombstone) }.map { opt =>
            (gtfr, opt)
          }
      }

    secondRoundActions.map { secondRound =>
      val tombstones: Seq[(Long, VfTombstone)] = (firstRoundActions ++ secondRound).flatMap {
        case (gtfr, tombstoneTypeOpt) => {

          val nonCompliantLimitedEngagementsOpt = gtfr.tweetResult match {
            case TweetFieldsResultState.Found(TweetFieldsResultFound(_, _, Some(filteredReason)))
                if FilteredReasonHelper.isLimitedEngagementsNonCompliant(filteredReason) =>
              Some(LimitedEngagements(LimitedEngagementReason.NonCompliant))
            case _ => None
          }

          (tombstoneTypeOpt, nonCompliantLimitedEngagementsOpt) match {
            case (Some(tombstoneType), nonCompliantOpt) =>
              val tombstoneId = tombstoneType.tombstoneId
              val action = tombstoneType.action
              val textOpt: Option[RichText] = action match {

                case InterstitialLimitedEngagements(_, _, Some(localizedMessage), _) =>
                  Some(LocalizedMessageToRichText(localizedMessage))
                case ipi: InterstitialLimitedEngagements =>
                  Some(
                    SafetyResultToUrtParser.fromSafetyResultToRichText(
                      SafetyResult(
                        Some(PublicInterest.ReasonToSafetyResultReason(ipi.reason)),
                        ipi.toActionThrift()
                      ),
                      languageTag
                    )
                  )

                case EmergencyDynamicInterstitial(_, _, Some(localizedMessage), _) =>
                  Some(LocalizedMessageToRichText(localizedMessage))
                case edi: EmergencyDynamicInterstitial =>
                  Some(
                    SafetyResultToUrtParser.fromSafetyResultToRichText(
                      SafetyResult(
                        None,
                        edi.toActionThrift()
                      ),
                      languageTag
                    )
                  )

                case Tombstone(epitaph, _) =>
                  if (request.useRichText)
                    Some(EpitaphToRichText(epitaph, languageTag))
                  else
                    Some(EpitaphToRichText(Epitaph.UnavailableWithoutLink, languageTag))

                case LocalizedTombstone(_, message) =>
                  if (request.useRichText)
                    Some(LocalizedMessageToRichText(LocalizedMessageConverter.toThrift(message)))
                  else
                    Some(EpitaphToRichText(Epitaph.UnavailableWithoutLink, languageTag))

                case Interstitial(_, Some(localizedMessage), _) =>
                  Some(LocalizedMessageToRichText.apply(localizedMessage))

                case interstitial: Interstitial =>
                  ReasonToUrtParser.fromReasonToRichText(interstitial.reason, languageTag)

                case _ =>
                  None
              }

              val isRoot: Boolean = gtfr.tweetId == request.conversationId
              val isOuter: Boolean = tombstoneId == request.conversationId
              val revealTextOpt: Option[RichText] = action match {
                case _: InterstitialLimitedEngagements | _: EmergencyDynamicInterstitial
                    if isRoot && isOuter =>
                  None

                case _: Interstitial | _: InterstitialLimitedEngagements |
                    _: EmergencyDynamicInterstitial =>
                  Some(ReasonToUrtParser.getRichRevealText(languageTag))

                case _ =>
                  None
              }

              val includeTweet = action match {
                case _: Interstitial | _: InterstitialLimitedEngagements |
                    _: EmergencyDynamicInterstitial =>
                  true
                case _ => false
              }

              val truncateForAction: Boolean =
                shouldTruncateDescendantsWhenFocal(action)
              val truncateForNonCompliant: Boolean =
                nonCompliantOpt
                  .map(shouldTruncateDescendantsWhenFocal).getOrElse(false)
              val truncateDescendants: Boolean =
                truncateForAction || truncateForNonCompliant

              val tombstone = textOpt match {
                case Some(_) if request.useRichText =>
                  VfTombstone(
                    includeTweet = includeTweet,
                    action = action,
                    tombstoneInfo = Some(
                      TombstoneInfo(
                        cta = None,
                        revealText = None,
                        richText = textOpt,
                        richRevealText = revealTextOpt
                      )
                    ),
                    tombstoneDisplayType = tombstoneType.tombstoneDisplayType,
                    truncateDescendantsWhenFocal = truncateDescendants
                  )
                case Some(_) =>
                  VfTombstone(
                    includeTweet = includeTweet,
                    action = action,
                    tombstoneInfo = Some(
                      TombstoneInfo(
                        text = textOpt
                          .map(richText => richText.text).getOrElse(
                            ""
                        cta = None,
                        revealText = revealTextOpt.map(_.text),
                        richText = None,
                        richRevealText = None
                      )
                    ),
                    tombstoneDisplayType = tombstoneType.tombstoneDisplayType,
                    truncateDescendantsWhenFocal = truncateDescendants
                  )

                case None =>
                  VfTombstone(
                    includeTweet = false,
                    action = action,
                    tombstoneInfo = Some(
                      TombstoneInfo(
                        cta = None,
                        revealText = None,
                        richText = Some(EpitaphToRichText(Epitaph.Unavailable, languageTag)),
                        richRevealText = None
                      )
                    ),
                    tombstoneDisplayType = tombstoneType.tombstoneDisplayType,
                    truncateDescendantsWhenFocal = truncateDescendants
                  )
              }

              Some((gtfr.tweetId, tombstone))

            case (None, Some(limitedEngagements))
                if shouldTruncateDescendantsWhenFocal(limitedEngagements) =>
              val tombstone = VfTombstone(
                tombstoneId = gtfr.tweetId,
                includeTweet = true,
                action = limitedEngagements,
                tombstoneInfo = None,
                tombstoneDisplayType = TombstoneDisplayType.NonCompliant,
                truncateDescendantsWhenFocal = true
              )
              Some((gtfr.tweetId, tombstone))

            case _ =>
              None
          }
        }
      }

      TombstoneVisibilityResponse(
        tweetVerdicts = tombstones.toMap
      )
    }
  }