From 80ffdf7e0672da9b6ae00764b35619dc5a64ddfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Otr=C4=99bski?= Date: Thu, 17 Sep 2020 22:29:54 +0200 Subject: [PATCH] Feedback on slack (#5) Sending feedback to slack --- build.sbt | 89 ++++++++------- docker-compose.yml | 2 + gui/Dockerfile | 2 - gui/public/favicon.ico | Bin 3870 -> 15406 bytes src/main/resources/application.conf | 9 ++ .../scala/quizz/feedback/FeedbackSender.scala | 101 ++++++++++++++++++ src/main/scala/quizz/web/WebApp.scala | 33 ++++-- 7 files changed, 187 insertions(+), 49 deletions(-) create mode 100644 src/main/scala/quizz/feedback/FeedbackSender.scala diff --git a/build.sbt b/build.sbt index 77ed8a6..441a940 100644 --- a/build.sbt +++ b/build.sbt @@ -12,24 +12,27 @@ lazy val quizz = .settings(settings) .settings( libraryDependencies ++= Seq( - library.scalaCheck % Test, - library.scalaTest % Test, - library.circeParser, - library.circeGeneric, - library.tapir, - library.tapirAkka, - library.tapirJson, - library.scalaLogging, - library.logback, - library.doobieCore, - library.doobiePostgres, - library.doobieQuill, - library.doobieScalatest % Test, + library.scalaCheck % Test, + library.scalaTest % Test, + library.circeParser, + library.circeGeneric, + library.tapir, + library.tapirAkka, + library.tapirJson, + library.sttpClient, + library.sttpClientCirce, + library.tapirJsonCirce, + library.scalaLogging, + library.logback, + library.doobieCore, + library.doobiePostgres, + library.doobieQuill, + library.doobieScalatest % Test // library.tapirHttp4s, // library.tapirJson, // library.bazelServer, // library.bazelClient - ) + ) ) // ***************************************************************************** @@ -41,29 +44,34 @@ lazy val library = object Version { val scalaCheck = "1.14.1" val scalaTest = "3.2.0" - val circe = "0.12.3" - val tapir = "0.11.11" + val circe = "0.12.3" + val tapir = "0.11.11" // val bazel = "0.20.0" val scalaLogging = "3.9.2" - val logback = "1.2.3" - val doobie = "0.9.0" + val logback = "1.2.3" + val doobie = "0.9.0" + val sttp = "2.2.8" } - val scalaCheck = "org.scalacheck" %% "scalacheck" % Version.scalaCheck - val scalaTest = "org.scalatest" %% "scalatest" % Version.scalaTest - val circeParser = "io.circe" %% "circe-parser" % Version.circe - val circeGeneric = "io.circe" %% "circe-generic" % Version.circe - val tapir = "com.softwaremill.tapir" %% "tapir-core" % Version.tapir - val tapirAkka = "com.softwaremill.tapir" %% "tapir-akka-http-server" % Version.tapir - val tapirJson = "com.softwaremill.tapir" %% "tapir-json-circe" % Version.tapir + val scalaCheck = "org.scalacheck" %% "scalacheck" % Version.scalaCheck + val scalaTest = "org.scalatest" %% "scalatest" % Version.scalaTest + val circeParser = "io.circe" %% "circe-parser" % Version.circe + val circeGeneric = "io.circe" %% "circe-generic" % Version.circe + val tapir = "com.softwaremill.tapir" %% "tapir-core" % Version.tapir + val tapirAkka = "com.softwaremill.tapir" %% "tapir-akka-http-server" % Version.tapir + val tapirJson = "com.softwaremill.tapir" %% "tapir-json-circe" % Version.tapir // val bazelClient = "org.http4s" %% "http4s-blaze-client" % Version.bazel // val bazelServer = "org.http4s" %% "http4s-blaze-server" % Version.bazel // val tapirHttp4s = "com.softwaremill.tapir" %% "tapir-http4s-server" % Version.tapir - val scalaLogging = "com.typesafe.scala-logging" %% "scala-logging" % Version.scalaLogging - val logback = "ch.qos.logback" % "logback-classic" % Version.logback - val doobieCore = "org.tpolecat" %% "doobie-core" % Version.doobie - val doobiePostgres = "org.tpolecat" %% "doobie-postgres" % Version.doobie - val doobieQuill = "org.tpolecat" %% "doobie-quill" % Version.doobie - val doobieScalatest = "org.tpolecat" %% "doobie-scalatest" % Version.doobie + val scalaLogging = "com.typesafe.scala-logging" %% "scala-logging" % Version.scalaLogging + val sttpClient = "com.softwaremill.sttp.client" %% "core" % Version.sttp + val sttpClientCirce = "com.softwaremill.sttp.client" %% "circe" % Version.sttp + val tapirJsonCirce = "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % Version.tapir + + val logback = "ch.qos.logback" % "logback-classic" % Version.logback + val doobieCore = "org.tpolecat" %% "doobie-core" % Version.doobie + val doobiePostgres = "org.tpolecat" %% "doobie-postgres" % Version.doobie + val doobieQuill = "org.tpolecat" %% "doobie-quill" % Version.doobie + val doobieScalatest = "org.tpolecat" %% "doobie-scalatest" % Version.doobie } // ***************************************************************************** @@ -82,19 +90,20 @@ lazy val commonSettings = startYear := Some(2019), licenses += ("Apache-2.0", url("http://www.apache.org/licenses/LICENSE-2.0")), scalacOptions ++= Seq( - "-unchecked", - "-deprecation", - "-language:_", - "-target:jvm-1.8", - "-encoding", "UTF-8", - ), + "-unchecked", + "-deprecation", + "-language:_", + "-target:jvm-1.8", + "-encoding", + "UTF-8" + ), Compile / unmanagedSourceDirectories := Seq((Compile / scalaSource).value), - Test / unmanagedSourceDirectories := Seq((Test / scalaSource).value), -) + Test / unmanagedSourceDirectories := Seq((Test / scalaSource).value) + ) lazy val scalafmtSettings = Seq( - scalafmtOnCompile := true, + scalafmtOnCompile := true ) mainClass := Some("quizz.web.WebApp") diff --git a/docker-compose.yml b/docker-compose.yml index 96740ac..2134ef3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,8 @@ services: - '/tmp/mindmups/:/tmp/mindmups/' environment: LOAD_FROM_DIR: "/tmp/mindmups/" + USE_SLACK: "false" + SLACK_TOKEN: "" gui: image: otrebski/quizz-gui:latest diff --git a/gui/Dockerfile b/gui/Dockerfile index 919b130..b0d4366 100644 --- a/gui/Dockerfile +++ b/gui/Dockerfile @@ -4,9 +4,7 @@ RUN apk add npm RUN npm --version COPY ./gui/ /workdir WORKDIR /workdir -RUN ls -la RUN npm install && npm run-script build -RUN ls -la build FROM nginx COPY ./gui/docker/default.conf /etc/nginx/conf.d/ diff --git a/gui/public/favicon.ico b/gui/public/favicon.ico index a11777cc471a4344702741ab1c8a588998b1311a..c270e5a908b204da7da6f180f780507666f9a037 100644 GIT binary patch literal 15406 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 literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 6c49c42..04b0a9b 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -3,4 +3,13 @@ quizz { dir = "" dir = ${?LOAD_FROM_DIR} } +} + +feedback { + slack { + use = false + use = ${?USE_SLACK} + token = "" + token = ${?SLACK_TOKEN} + } } \ No newline at end of file diff --git a/src/main/scala/quizz/feedback/FeedbackSender.scala b/src/main/scala/quizz/feedback/FeedbackSender.scala new file mode 100644 index 0000000..926f971 --- /dev/null +++ b/src/main/scala/quizz/feedback/FeedbackSender.scala @@ -0,0 +1,101 @@ +package quizz.feedback + +import cats.effect.Sync +import cats.syntax.option._ +import com.typesafe.scalalogging.LazyLogging +import quizz.web.WebApp.Api.{ Feedback, QuizzState } + +import scala.language.higherKinds + +trait FeedbackSender[F[_]] { + + def send(feedback: Feedback, quizzState: QuizzState): F[Unit] +} + +class LogFeedbackSender[F[_]: Sync] extends FeedbackSender[F] with LazyLogging { + override def send(feedback: Feedback, quizzState: QuizzState): F[Unit] = + Sync[F].delay(logger.info(s"Have feedback: $feedback for $quizzState")) +} + +object SlackFeedbackSender { + case class SlackMessage(blocks: List[Block]) + case class Block(text: Text, `type`: String = "section", block_id: Option[String] = None) + case class Text(text: String, `type`: String = "mrkdwn") + + private def feedbackIcon(feedback: Feedback): String = + feedback.rate match { + case a if a > 0 => ":+1:" + case a if a < 0 => ":-1:" + case a if a == 0 => ":point_right:" + } + + def convertFeedback(feedback: Feedback, quizzState: QuizzState): SlackMessage = { + val history: List[Block] = quizzState.history.foldRight(List.empty[Block]) { + (historyStep, list) => + val answers = historyStep.answers + .map(answer => + s" - ${answer.selected.map(if (_) ":ballot_box_with_check:" else ":black_square_button:").getOrElse(":black_square_button:")} ${answer.text}" + ) + .mkString("\n") + Block( + Text( + s"${historyStep.question}\n$answers" + ), + block_id = historyStep.id.some + ) :: list + } + val answers = quizzState.currentStep.answers + .map(answer => + s" - ${answer.selected.map(if (_) ":heavy_check_mark:" else "").getOrElse("")} ${answer.text}" + ) + .mkString("\n") + val lastQuestion = Block( + Text( + s"${quizzState.currentStep.question}\n$answers" + ), + block_id = quizzState.currentStep.id.some + ) + val header = Block( + Text( + s"Feedback ${feedbackIcon(feedback)} for quiz ${feedback.quizzId}", + `type` = "plain_text" + ), + block_id = "header".some, + `type` = "header" + ) + val comment = Block( + Text(s"Comment: ${feedback.comment}"), + block_id = "comment".some + ) + SlackMessage(header :: comment :: history ::: List(lastQuestion)) + } +} + +class SlackFeedbackSender[F[_]: Sync](token: String) extends FeedbackSender[F] { + + override def send(feedback: Feedback, quizzState: QuizzState): F[Unit] = + Sync[F].delay { + import sttp.client.circe._ + + val url: String = s"https://hooks.slack.com/services/$token" + import sttp.client._ + val uri = uri"$url" + val rate = feedback.rate match { + case a if a > 0 => ":+1:" + case a if a < 0 => ":-1:" + case a if a == 0 => ":point_right:" + } + + val message = SlackFeedbackSender.convertFeedback(feedback, quizzState) + import io.circe.generic.auto._ + + val myRequest = basicRequest + .post(uri) + .header("Content-type", "application/json") + .body(message) + + implicit val backend: SttpBackend[Identity, Nothing, NothingT] = HttpURLConnectionBackend() + val response: Identity[Response[Either[String, String]]] = myRequest.send() + response + } +} diff --git a/src/main/scala/quizz/web/WebApp.scala b/src/main/scala/quizz/web/WebApp.scala index 850188f..773d3df 100644 --- a/src/main/scala/quizz/web/WebApp.scala +++ b/src/main/scala/quizz/web/WebApp.scala @@ -27,8 +27,8 @@ import com.typesafe.config.{ Config, ConfigFactory } import com.typesafe.scalalogging.LazyLogging import io.circe import io.circe.generic.auto._ -import mindmup.Parser import quizz.data.{ ExamplesData, Loader } +import quizz.feedback.{ LogFeedbackSender, SlackFeedbackSender } import quizz.model.Quizz import quizz.web.WebApp.Api.{ AddQuizzResponse, FeedbackResponse, Quizzes } import tapir.json.circe._ @@ -164,9 +164,8 @@ object WebApp extends IOApp with LazyLogging { Parser.parseInput(request.mindmupSource).map(_.toQuizz.copy(id = request.id)) newQuizzOrError match { - case Left(error) => Future.failed(new Exception(error.toString)) + case Left(error) => Future.failed(new Exception(error.toString)) case Right(quizz) => -// logger.info(s"Adding quizz $quizz") val io: IO[Either[String, Map[String, Quizz]]] = quizzes.getAndUpdate(old => old.map(_.updated(request.id, quizz))) io.map(_ => AddQuizzResponse("Added").asRight) @@ -174,9 +173,29 @@ object WebApp extends IOApp with LazyLogging { } } - def feedbackProvider(feedback: Api.Feedback): Future[Either[Unit, FeedbackResponse]] = { - logger.info(s"Have feedback: $feedback") - Future.successful(Right(FeedbackResponse("OK"))) + def feedbackProvider( + quizzes: Ref[IO, Either[String, Map[String, Quizz]]] + )(feedback: Api.Feedback): Future[Either[Unit, FeedbackResponse]] = { + val log = new LogFeedbackSender[IO] + val useSlack = config.getBoolean("feedback.slack.use") + val request = Api.QuizzQuery(feedback.quizzId, feedback.path) + val quizzState: IO[Either[String, Api.QuizzState]] = for { + q <- quizzes.get + result = q.flatMap(quizzes => Logic.calculateStateOnPath(request, quizzes)) + } yield result + + val p = quizzState.flatMap { + case Right(quizzState) => + if (useSlack) { + val slack = new SlackFeedbackSender[IO](config.getString("feedback.slack.token")) + log.send(feedback, quizzState).flatMap(_ => slack.send(feedback, quizzState)) + } else + log.send(feedback, quizzState) + case Left(error) => IO.raiseError(new Exception(s"Can't process feedback: $error")) + } + + implicit val ec: ExecutionContextExecutor = scala.concurrent.ExecutionContext.global + p.unsafeToFuture().map { _ => Right(FeedbackResponse("OK")) } } override def run(args: List[String]): IO[ExitCode] = { @@ -191,7 +210,7 @@ object WebApp extends IOApp with LazyLogging { val route = routeEndpoint.toRoute(routeWithPathProvider(quizzes)) val routeStart = routeEndpointStart.toRoute(routeWithoutPathProvider(quizzes)) val routeList = listQuizzes.toRoute(quizListProvider(quizzes)) - val routeFeedback = feedback.toRoute(feedbackProvider) + val routeFeedback = feedback.toRoute(feedbackProvider(quizzes)) val add = addQuizz.toRoute(addQuizzProvider(quizzes)) implicit val system: ActorSystem = ActorSystem("my-system") implicit val materializer: ActorMaterializer = ActorMaterializer()