Skip to content
This repository has been archived by the owner on Jul 26, 2021. It is now read-only.

Commit

Permalink
Make Paths into an Akka Actor
Browse files Browse the repository at this point in the history
  • Loading branch information
Mario Galic committed Apr 11, 2017
1 parent 3f434b6 commit d415b4d
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 192 deletions.
5 changes: 3 additions & 2 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ object Dependencies {
lazy val listJson = "net.liftweb" %% "lift-json" % "3.1.0-M1"
lazy val ficus = "com.iheart" %% "ficus" % "1.4.0"
lazy val yaml = "net.jcazevedo" %% "moultingyaml" % "0.4.0"

lazy val dependencies = Seq(scalaTest, logback, scalaLogging, okhttp, listJson, ficus, yaml)
lazy val akka = "com.typesafe.akka" %% "akka-actor" % "2.4.17"
lazy val akkaLog = "com.typesafe.akka" %% "akka-slf4j" % "2.4.17"
lazy val dependencies = Seq(scalaTest, logback, scalaLogging, okhttp, listJson, ficus, yaml, akka, akkaLog)
}
5 changes: 1 addition & 4 deletions src/main/scala/com/gu/tip/Notifier.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ trait NotifierIf { this: GitHubApiIf =>
}

trait Notifier extends NotifierIf with LazyLogging { this: GitHubApiIf =>

def setLabelOnLatestMergedPr(): Int = setGitHubLabel(getLastMergedPullRequestNumber())

/*
Expand All @@ -25,10 +24,8 @@ trait Notifier extends NotifierIf with LazyLogging { this: GitHubApiIf =>
private def setGitHubLabel(prNumber: String): Int = {
val responseCode = setLabel(prNumber)
if (responseCode == 200) {
logger.info("Verification label added to PR successfully")
logger.info(s"Successfully set verification label on PR $prNumber")
}
responseCode
}


}
118 changes: 118 additions & 0 deletions src/main/scala/com/gu/tip/PathReader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.gu.tip

import java.io.FileNotFoundException

import com.typesafe.scalalogging.LazyLogging
import net.jcazevedo.moultingyaml._
import net.jcazevedo.moultingyaml.DefaultYamlProtocol

import scala.io.Source._
import scala.util.Try
import akka.actor.{Actor, ActorRef, ActorSystem, Props}

// $COVERAGE-OFF$
case class Path(name: String, description: String)

object TipYamlProtocol extends DefaultYamlProtocol {
implicit val pathFormat = yamlFormat2(Path)
}
// $COVERAGE-ON$

class EnrichedPath(path: Path) extends LazyLogging {
def verify() = {
logger.info(s"${path.name} is now verified")
_verified = true
}

def verified(): Boolean = _verified

private var _verified = false
}

sealed trait PathsActorMessage

// OK
case class Verify(pathName: String) extends PathsActorMessage
case class PathIsVerified(pathName: String) extends PathsActorMessage
case class PathIsUnverified(pathName: String) extends PathsActorMessage
case class PathIsAlreadyVerified(pathName: String) extends PathsActorMessage
case object AllPathsVerified extends PathsActorMessage
case object AllPathsAlreadyVerified extends PathsActorMessage
case object NumberOfPaths extends PathsActorMessage
case class NumberOfPathsAnswer(value: Int) extends PathsActorMessage
case object NumberOfVerifiedPaths extends PathsActorMessage
case class NumberOfVerifiedPathsAnswer(value: Int) extends PathsActorMessage

// NOK
case class PathDoesNotExist(pathName: String) extends PathsActorMessage

class PathsActor(val paths: Map[String, EnrichedPath]) extends Actor with LazyLogging{
private def allVerified: Boolean = _unverifiedPathCount == 0

private var _unverifiedPathCount = paths.size

def receive = {
case Verify(pathname) =>
logger.trace("Paths got Verify message")

Try(paths(pathname)).map { path =>
if (allVerified) {
logger.trace(s"All paths are already verified")
sender() ! AllPathsAlreadyVerified
}
else
if (path.verified()) {
logger.trace(s"Path ${pathname} is already verified")
sender() ! PathIsAlreadyVerified(pathname)
}
else {
path.verify()
_unverifiedPathCount = _unverifiedPathCount - 1
if (allVerified) {
logger.trace("All paths are verified!")
sender() ! AllPathsVerified
}
else {
logger.trace(s"Path ${pathname} is verified!")
sender() ! PathIsVerified(pathname)
}
}
} recover { case e: NoSuchElementException =>
logger.error(s"Unrecognized path name. Available paths: ${paths.map(_._1).mkString(",")}")
sender() ! PathDoesNotExist(pathname)
}

case NumberOfPaths => sender() ! NumberOfPathsAnswer(paths.size)

case NumberOfVerifiedPaths => sender() ! NumberOfVerifiedPathsAnswer(paths.size - _unverifiedPathCount)
}
}

trait PathReaderIf {
def readPaths(filename: String)(implicit system: ActorSystem): ActorRef
}

/**
* Reader for path config file with the following syntax:
*
* - name: Buy Subscription
* description: User completes subscription purchase journey
*
* - name: Register Account
* description: User completes account registration journey
*/
trait PathReader extends PathReaderIf with LazyLogging {
import TipYamlProtocol._

private def readFile(filename: String): String =
Option(getClass.getClassLoader.getResource(filename)).map {
path => fromFile(path.getPath).mkString
}.getOrElse(throw new FileNotFoundException(s"Path definition file not found on the classpath: $filename"))

def readPaths(filename: String)(implicit system: ActorSystem): ActorRef = {
val pathList = readFile(filename).parseYaml.convertTo[List[Path]]
val paths: Map[String, EnrichedPath] = pathList.map(path => path.name -> new EnrichedPath(path)).toMap
system.actorOf(Props[PathsActor](new PathsActor(paths)))
}
}

103 changes: 58 additions & 45 deletions src/main/scala/com/gu/tip/Tip.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,63 +6,76 @@ import com.typesafe.scalalogging.LazyLogging
import net.jcazevedo.moultingyaml.DeserializationException

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.util.Try
import akka.actor.{ActorRef, ActorSystem}
import akka.pattern.ask
import akka.util.Timeout

sealed trait TipResponse

// OK
case object PathVerified extends TipResponse
case object LabelAlreadySet extends TipResponse
case object LabelSet extends TipResponse
case object LabelSet extends TipResponse
case class PathsActorResponse(msg: PathsActorMessage) extends TipResponse

// NOK
case object FailedToSetLabel extends TipResponse
case object UnrecognizedPathName extends TipResponse
case object PathDefinitionSyntaxError extends TipResponse
case object PathDefinitionFileNotFound extends TipResponse
case object UnclassifiedError extends TipResponse
case object FailedToSetLabel extends TipResponse
case object PathDefinitionSyntaxError extends TipResponse
case object PathDefinitionFileNotFound extends TipResponse
case class PathNotFound(name: String) extends TipResponse
case object UnclassifiedError extends TipResponse

trait TipIf { this: NotifierIf =>
trait TipIf { this: PathReaderIf with NotifierIf =>
def verify(pathName: String): Future[TipResponse]

val pathConfigFilename = "tip.yaml"

implicit val system = ActorSystem()
implicit val ec = system.dispatcher
implicit val timeout = Timeout(10.second)
lazy val pathsActorTry: Try[ActorRef] = Try(readPaths(pathConfigFilename))
}

trait Tip extends TipIf with LazyLogging { this: NotifierIf =>
def verify(pathName: String): Future[TipResponse] = Future {
if (!_labelSet) {
paths.verify(pathName)
if (paths.allVerified) {
_labelSet = true
if (setLabelOnLatestMergedPr() != 200)
FailedToSetLabel
else
trait Tip extends TipIf with LazyLogging { this: PathReaderIf with NotifierIf =>
def verify(pathName: String): Future[TipResponse] = {
(pathsActorTry.map { pathsActor =>
pathsActor ? Verify(pathName) map {
case AllPathsVerified =>
if (setLabelOnLatestMergedPr() == 200) {
logger.info("Successfully verified all paths!")
LabelSet
}
else
PathVerified
} else
LabelAlreadySet
} recover {
case e: NoSuchElementException =>
logger.error(s"Unrecognized path name. Available paths: ${paths.paths.map(_._1).mkString(",")}", e)
UnrecognizedPathName

case e: DeserializationException =>
logger.error(s"Syntax problem with path config ${pathConfigFilename} file. Refer to README for the correct syntax: ", e)
PathDefinitionSyntaxError

case e: FileNotFoundException =>
logger.error(s"Path config ${pathConfigFilename} file not found. Place ${pathConfigFilename} at project base directory level: ", e)
PathDefinitionFileNotFound

case e =>
logger.error(s"Unclassified Tip error: ", e)
UnclassifiedError
}

private lazy val paths = YamlPathConfigReader(pathConfigFilename)
private var _labelSet = false
}
else
FailedToSetLabel

case PathDoesNotExist(pathname) =>
logger.error(s"Unrecognized path name: $pathname")
PathNotFound(pathname)

case response: PathsActorMessage =>
logger.trace(s"Tip received response from PathsActor: $response")
PathsActorResponse(response)

} recover { // exceptions thrown from Notifier
case e =>
logger.error(s"Unclassified Tip error: ", e)
UnclassifiedError
}
} recover { // exceptions thrown from PathReader
case e: DeserializationException =>
logger.error(s"Syntax problem with path config ${pathConfigFilename} file. Refer to README for the correct syntax: ", e)
Future(PathDefinitionSyntaxError)

case e: FileNotFoundException =>
logger.error(s"Path config ${pathConfigFilename} file not found. Place ${pathConfigFilename} at project base directory level: ", e)
Future(PathDefinitionFileNotFound)

case e =>
logger.error(s"Unclassified Tip error: ", e)
Future(UnclassifiedError)
}).get
}
}

object Tip extends Tip with Notifier with GitHubApi with HttpClient
object Tip extends Tip with PathReader with Notifier with GitHubApi with HttpClient

67 changes: 0 additions & 67 deletions src/main/scala/com/gu/tip/YamlPathConfigReader.scala

This file was deleted.

Loading

0 comments on commit d415b4d

Please sign in to comment.