kopia lustrzana https://github.com/twitter/the-algorithm
175 wiersze
6.4 KiB
Scala
175 wiersze
6.4 KiB
Scala
package com.twitter.tweetypie.util
|
|
|
|
import com.twitter.servo.util.Gate
|
|
import com.twitter.tweetypie.util.TweetEditFailure.TweetEditInvalidEditControlException
|
|
import com.twitter.tweetypie.util.TweetEditFailure.TweetEditUpdateEditControlException
|
|
import com.twitter.tweetypie.thriftscala.EditControl
|
|
import com.twitter.tweetypie.thriftscala.EditControlEdit
|
|
import com.twitter.tweetypie.thriftscala.EditControlInitial
|
|
import com.twitter.tweetypie.thriftscala.Tweet
|
|
import com.twitter.util.Try
|
|
import com.twitter.util.Return
|
|
import com.twitter.util.Throw
|
|
import com.twitter.util.Time
|
|
import com.twitter.util.Duration
|
|
|
|
object EditControlUtil {
|
|
|
|
val maxTweetEditsAllowed = 5
|
|
val oldEditTimeWindow = Duration.fromMinutes(30)
|
|
val editTimeWindow = Duration.fromMinutes(60)
|
|
|
|
def editControlEdit(
|
|
initialTweetId: TweetId,
|
|
editControlInitial: Option[EditControlInitial] = None
|
|
): EditControl.Edit =
|
|
EditControl.Edit(
|
|
EditControlEdit(initialTweetId = initialTweetId, editControlInitial = editControlInitial))
|
|
|
|
// EditControl for the tweet that is not an edit, that is, any regular tweet we create
|
|
// that can, potentially, be edited later.
|
|
def makeEditControlInitial(
|
|
tweetId: TweetId,
|
|
createdAt: Time,
|
|
setEditWindowToSixtyMinutes: Gate[Unit] = Gate(_ => false)
|
|
): EditControl.Initial = {
|
|
val editWindow = if (setEditWindowToSixtyMinutes()) editTimeWindow else oldEditTimeWindow
|
|
val initial = EditControlInitial(
|
|
editTweetIds = Seq(tweetId),
|
|
editableUntilMsecs = Some(createdAt.plus(editWindow).inMilliseconds),
|
|
editsRemaining = Some(maxTweetEditsAllowed),
|
|
isEditEligible = defaultIsEditEligible,
|
|
)
|
|
EditControl.Initial(initial)
|
|
}
|
|
|
|
// Returns if a given latestTweetId is the latest edit in the EditControl
|
|
def isLatestEdit(
|
|
tweetEditControl: Option[EditControl],
|
|
latestTweetId: TweetId
|
|
): Try[Boolean] = {
|
|
tweetEditControl match {
|
|
case Some(EditControl.Initial(initial)) =>
|
|
isLatestEditFromEditControlInitial(Some(initial), latestTweetId)
|
|
case Some(EditControl.Edit(edit)) =>
|
|
isLatestEditFromEditControlInitial(
|
|
edit.editControlInitial,
|
|
latestTweetId
|
|
)
|
|
case _ => Throw(TweetEditInvalidEditControlException)
|
|
}
|
|
}
|
|
|
|
// Returns if a given latestTweetId is the latest edit in the EditControlInitial
|
|
private def isLatestEditFromEditControlInitial(
|
|
initialTweetEditControl: Option[EditControlInitial],
|
|
latestTweetId: TweetId
|
|
): Try[Boolean] = {
|
|
initialTweetEditControl match {
|
|
case Some(initial) =>
|
|
Return(latestTweetId == initial.editTweetIds.last)
|
|
case _ => Throw(TweetEditInvalidEditControlException)
|
|
}
|
|
}
|
|
|
|
/* Create an updated edit control for an initialTweet given the id of the new edit */
|
|
def editControlForInitialTweet(
|
|
initialTweet: Tweet,
|
|
newEditId: TweetId
|
|
): Try[EditControl.Initial] = {
|
|
initialTweet.editControl match {
|
|
case Some(EditControl.Initial(initial)) =>
|
|
Return(EditControl.Initial(plusEdit(initial, newEditId)))
|
|
|
|
case Some(EditControl.Edit(_)) => Throw(TweetEditUpdateEditControlException)
|
|
|
|
case _ =>
|
|
initialTweet.coreData match {
|
|
case Some(coreData) =>
|
|
Return(
|
|
makeEditControlInitial(
|
|
tweetId = initialTweet.id,
|
|
createdAt = Time.fromMilliseconds(coreData.createdAtSecs * 1000),
|
|
setEditWindowToSixtyMinutes = Gate(_ => true)
|
|
)
|
|
)
|
|
case None => Throw(new Exception("Tweet Missing Required CoreData"))
|
|
}
|
|
}
|
|
}
|
|
|
|
def updateEditControl(tweet: Tweet, newEditId: TweetId): Try[Tweet] =
|
|
editControlForInitialTweet(tweet, newEditId).map { editControl =>
|
|
tweet.copy(editControl = Some(editControl))
|
|
}
|
|
|
|
def plusEdit(initial: EditControlInitial, newEditId: TweetId): EditControlInitial = {
|
|
val newEditTweetIds = (initial.editTweetIds :+ newEditId).distinct.sorted
|
|
val editsCount = newEditTweetIds.size - 1 // as there is the original tweet ID there too.
|
|
initial.copy(
|
|
editTweetIds = newEditTweetIds,
|
|
editsRemaining = Some(maxTweetEditsAllowed - editsCount),
|
|
)
|
|
}
|
|
|
|
// The ID of the initial Tweet if this is an edit
|
|
def getInitialTweetIdIfEdit(tweet: Tweet): Option[TweetId] = tweet.editControl match {
|
|
case Some(EditControl.Edit(edit)) => Some(edit.initialTweetId)
|
|
case _ => None
|
|
}
|
|
|
|
// If this is the first tweet in an edit chain, return the same tweet id
|
|
// otherwise return the result of getInitialTweetId
|
|
def getInitialTweetId(tweet: Tweet): TweetId =
|
|
getInitialTweetIdIfEdit(tweet).getOrElse(tweet.id)
|
|
|
|
def isInitialTweet(tweet: Tweet): Boolean =
|
|
getInitialTweetId(tweet) == tweet.id
|
|
|
|
// Extracted just so that we can easily track where the values of isEditEligible is coming from.
|
|
private def defaultIsEditEligible: Option[Boolean] = Some(true)
|
|
|
|
// returns true if it's an edit of a Tweet or an initial Tweet that's been edited
|
|
def isEditTweet(tweet: Tweet): Boolean =
|
|
tweet.editControl match {
|
|
case Some(eci: EditControl.Initial) if eci.initial.editTweetIds.size <= 1 => false
|
|
case Some(_: EditControl.Initial) | Some(_: EditControl.Edit) | Some(
|
|
EditControl.UnknownUnionField(_)) =>
|
|
true
|
|
case None => false
|
|
}
|
|
|
|
// returns true if editControl is from an edit of a Tweet
|
|
// returns false for any other state, including edit intial.
|
|
def isEditControlEdit(editControl: EditControl): Boolean = {
|
|
editControl match {
|
|
case _: EditControl.Edit | EditControl.UnknownUnionField(_) => true
|
|
case _ => false
|
|
}
|
|
}
|
|
|
|
def getEditTweetIds(editControl: Option[EditControl]): Try[Seq[TweetId]] = {
|
|
editControl match {
|
|
case Some(EditControl.Edit(EditControlEdit(_, Some(eci)))) =>
|
|
Return(eci.editTweetIds)
|
|
case Some(EditControl.Initial(initial)) =>
|
|
Return(initial.editTweetIds)
|
|
case _ =>
|
|
Throw(new Exception(s"EditControlInitial not found in $editControl"))
|
|
}
|
|
}
|
|
}
|
|
|
|
object TweetEditFailure {
|
|
abstract class TweetEditException(msg: String) extends Exception(msg)
|
|
|
|
case object TweetEditGetInitialEditControlException
|
|
extends TweetEditException("Initial EditControl not found")
|
|
|
|
case object TweetEditInvalidEditControlException
|
|
extends TweetEditException("Invalid EditControl for initial_tweet")
|
|
|
|
case object TweetEditUpdateEditControlException
|
|
extends TweetEditException("Invalid Edit Control Update")
|
|
}
|